From cdada6c68f9740d13dd6674bcb658e28e68253b6 Mon Sep 17 00:00:00 2001 From: Feiyang Date: Tue, 24 Aug 2021 13:56:01 -0700 Subject: [PATCH] Repo refactoring (#5345) * Split storage and storage-compat (#5271) * separate storage-compat from storage * commit * fix tests * wat? * build storage-compat * save * fixing some compat tests * format * update import path * format * get compat tests to work * format * update package json * cleanup * format * address comments * Update packages/storage-compat/test/unit/service.test.ts Co-authored-by: Christina Holland * add missing dev dep * Update packages/storage-compat/package.json * Update packages/storage-compat/package.json * Update deps after v8 release * update compat version * Split database and database-compat (#5276) * compile database * pass database tests * compile and test database-compat * pass all tests * prettier * cleanup * fix lint * address comments * what is going on with ci * use correct case in import path * uppercase * rename * fix component name * Repo refactoring for v9 (#5325) * rename folders * rename folders * migrate app and app-compat * migrate installations and analytics * migrate app check * migrate auth * migrate functions * migrate messaging * migrate performance * migrate remoteconfig * remove v8 code * update versions * migrate firebase * fix merge issues * save * fix firebase builds * update changeset config * update build scripts * update release script * fix functions typings * remove redundant typings * update path * treat external dependencies correctly * fix lint * remove firestore-compat references * fix build issues * update build scripts * update pkg json * fix test * fix some tests * fix some tests * fix integration tests * fixes * fix dep * update ci tests * resolve todos * remove exp references * docgen build * export FirebaseError (#5349) * firebase packaging update (#5348) * Fei v9 main firestore (#5319) * compile firestore * update typings path * compile firestore compat * lint compat * make test work * get most tests work * build * remove special paths * update firestore-compat pkg json * fix merge issues * Simplify bundles * Prettier * Fix Bundle compile * Fix build * address comments * console build * Fix all tests * Prettier * fix merge issues * fix typo * update paths * app-exp * fix lint * correct path * fix * fix compat lint * fix firestore integration * fix workflow * update dep * Always block on Auth (#5340) * Always block on Auth * Don't block on token if already recevied * fix lint * build firestore * remove memory only tests * fix firebase integration tests * enable more build and tests * add firestore-compat to firebase dep * fix auth compat class * enable auth test * auth package doc * Update API reports Co-authored-by: Sebastian Schmidt Co-authored-by: Feiyang1 * rebasing * Create registerMessagingCompat.ts * Add changeset for v9 (#5350) * add changeset * publish messaging interop * add firestore bumps * update changeset * Revert "Create registerMessagingCompat.ts" This reverts commit 0b95a5167e403ffc0dbcd9dd7dcb7cda754e510d. * Revert "rebasing" This reverts commit a8bf697157aaa29e8450acb626aae81ad5b542e1. * Update index.d.ts (#5355) * Fix Context Check in `Messaging-Compat` (#5353) * Add Rules Unit Testing v2 skeleton for v9 release (#5352) * Reset package for next major version. * Switch emulator script to node-fetch. * Migrate the other request call to fetch. * Update packages/rules-unit-testing/package.json Co-authored-by: Feiyang * Create stale-ducks-live.md * Revert version change. Co-authored-by: Feiyang * fix import path (#5356) * Add new types and function stubs for RUT vNext. (#5316) * Add new types and function stubs. * Fix types for testEnv.emulators. * Add util functions. * Add withFunctionTriggersDisabled overloads. * Improve typing for EmulatorConfig. * Fix tests. * Rename test_environment.ts to initialize.ts. * Add a dummy test to make CI pass. * Implement emulators discovery in RUTv2. (#5334) * Add new types and function stubs. * Fix types for testEnv.emulators. * Add util functions. * Add withFunctionTriggersDisabled overloads. * Improve typing for EmulatorConfig. * Fix tests. * Rename test_environment.ts to initialize.ts. * Add a dummy test to make CI pass. * Implement emulators discovery. * Use URL object from global. * Fix unreachable error code. * Implement most of RUTv2 features. (#5343) * Implement most of RUTv2 features. * Switch to compat instances. * Remove legacy code. * Use public typings for release (#5358) * use public typings for storage * use public typings for database * Implement rest of RUTv2 features. (#5360) * Implement loading rules and withFunctionTriggersDisabled. * Implement clearFirestore and storage. * Add missing await. * Add default bucketUrl. * Use alternative method to clear bucket. * Use default param (review feedback). * Storage typing updates (#5359) * rename to StorageError * more type safety * Update API reports * update api report Co-authored-by: Feiyang1 * correct component name * fix installtions-compat typing * remove rules-unit-testing from ignore list * reenable things * support mjs files * correct matching pattern * transform only @firebase/util * use public types for database doc * transform all but compat packages Co-authored-by: Sebastian Schmidt Co-authored-by: Feiyang1 Co-authored-by: kai Co-authored-by: Yuchen Shi Co-authored-by: Kai Wu --- .changeset/config.json | 19 - .changeset/stale-ducks-live.md | 5 + .changeset/tame-olives-compete.md | 43 + .github/CODEOWNERS | 41 +- .../test-changed-fcm-integration.yml | 2 +- .../test-changed-firestore-integration.yml | 2 +- .github/workflows/test-changed-firestore.yml | 2 +- .github/workflows/test-changed-misc.yml | 2 +- .github/workflows/test-changed.yml | 2 +- .../workflows/test-firebase-integration.yml | 2 +- .github/workflows/update-api-reports.yml | 3 +- .gitignore | 2 - ...{analytics-exp.api.md => analytics.api.md} | 820 +- ...{app-check-exp.api.md => app-check.api.md} | 188 +- .../api-review/{app-exp.api.md => app.api.md} | 227 +- .../{auth-exp.api.md => auth.api.md} | 31 +- common/api-review/firestore-lite.api.md | 4 +- common/api-review/firestore.api.md | 1042 +- ...{functions-exp.api.md => functions.api.md} | 98 +- ...ations-exp.api.md => installations.api.md} | 5 +- ...ging-exp.sw.api.md => messaging-sw.api.md} | 125 +- ...{messaging-exp.api.md => messaging.api.md} | 139 +- ...formance-exp.api.md => performance.api.md} | 5 +- ...config-exp.api.md => remote-config.api.md} | 5 +- common/api-review/storage.api.md | 125 +- config/webpack.test.js | 2 +- integration/compat-interop/analytics.test.ts | 2 +- integration/compat-interop/app.test.ts | 2 +- integration/compat-interop/auth.test.ts | 2 +- integration/compat-interop/functions.test.ts | 2 +- integration/compat-interop/messaging.test.ts | 2 +- integration/compat-interop/package.json | 14 +- .../compat-interop/performance.test.ts | 2 +- .../compat-interop/remote-config.test.ts | 2 +- integration/compat-typings/package.json | 2 +- integration/compat-typings/typings.ts | 4 +- integration/firebase/test/namespace.test.ts | 2 +- .../firebase/test/namespaceDefinition.json | 11 +- integration/firestore/firebase_export.ts | 5 +- .../firestore/firebase_export_memory.ts | 71 - integration/firestore/gulpfile.js | 5 +- integration/firestore/package.json | 2 +- lerna.json | 1 - package.json | 15 +- .../analytics-compat/rollup.config.js | 58 - .../analytics-compat/rollup.config.release.js | 73 - .../analytics-compat/rollup.shared.js | 54 - packages-exp/analytics-exp/.eslintrc.js | 33 - packages-exp/analytics-exp/karma.conf.js | 32 - .../analytics-exp/karma.integration.conf.js | 32 - packages-exp/analytics-exp/package.json | 67 - packages-exp/analytics-exp/rollup.config.js | 58 - .../analytics-exp/rollup.config.release.js | 73 - packages-exp/analytics-exp/rollup.shared.js | 54 - packages-exp/analytics-exp/src/constants.ts | 38 - packages-exp/analytics-exp/src/errors.ts | 86 - packages-exp/analytics-exp/src/factory.ts | 237 - .../analytics-exp/src/functions.test.ts | 173 - packages-exp/analytics-exp/src/functions.ts | 141 - .../analytics-exp/src/get-config.test.ts | 259 - packages-exp/analytics-exp/src/get-config.ts | 320 - .../analytics-exp/src/helpers.test.ts | 499 - packages-exp/analytics-exp/src/helpers.ts | 320 - .../testing/get-fake-firebase-services.ts | 86 - .../analytics-exp/testing/gtag-script-util.ts | 28 - .../testing/integration-tests/integration.ts | 89 - packages-exp/analytics-exp/testing/setup.ts | 23 - .../app-check-compat/rollup.config.js | 58 - .../app-check-compat/rollup.config.release.js | 67 - .../app-check-compat/rollup.shared.js | 54 - packages-exp/app-check-compat/tsconfig.json | 8 - packages-exp/app-check-exp/README.md | 5 - packages-exp/app-check-exp/package.json | 64 - packages-exp/app-check-exp/rollup.config.js | 58 - .../app-check-exp/rollup.config.release.js | 73 - packages-exp/app-check-exp/rollup.shared.js | 54 - packages-exp/app-check-exp/src/api.test.ts | 315 - packages-exp/app-check-exp/src/api.ts | 257 - packages-exp/app-check-exp/src/client.test.ts | 199 - packages-exp/app-check-exp/src/client.ts | 134 - packages-exp/app-check-exp/src/constants.ts | 38 - packages-exp/app-check-exp/src/debug.test.ts | 86 - packages-exp/app-check-exp/src/debug.ts | 66 - packages-exp/app-check-exp/src/errors.ts | 73 - packages-exp/app-check-exp/src/factory.ts | 62 - packages-exp/app-check-exp/src/index.ts | 85 - packages-exp/app-check-exp/src/indexeddb.ts | 151 - .../app-check-exp/src/internal-api.test.ts | 494 - .../app-check-exp/src/internal-api.ts | 314 - packages-exp/app-check-exp/src/logger.ts | 20 - .../src/proactive-refresh.test.ts | 201 - .../app-check-exp/src/proactive-refresh.ts | 121 - packages-exp/app-check-exp/src/providers.ts | 149 - .../app-check-exp/src/recaptcha.test.ts | 128 - packages-exp/app-check-exp/src/recaptcha.ts | 139 - packages-exp/app-check-exp/src/state.ts | 75 - .../app-check-exp/src/storage.test.ts | 70 - packages-exp/app-check-exp/src/storage.ts | 98 - packages-exp/app-check-exp/src/util.ts | 44 - packages-exp/app-check-exp/test/util.ts | 144 - packages-exp/app-check-exp/tsconfig.json | 8 - .../app-compat/rollup.config.release.js | 122 - packages-exp/app-exp/karma.conf.js | 35 - packages-exp/app-exp/package.json | 62 - packages-exp/app-exp/rollup.config.release.js | 73 - packages-exp/app-exp/rollup.shared.js | 56 - packages-exp/app-exp/src/constants.ts | 78 - packages-exp/app-exp/src/errors.ts | 56 - packages-exp/app-exp/src/firebaseApp.ts | 106 - packages-exp/app-exp/src/logger.ts | 20 - .../app-exp/src/platformLoggerService.ts | 58 - .../app-exp/src/registerCoreComponents.ts | 37 - .../auth-compat-exp/demo/public/index.html | 767 - .../demo/public/service-worker.js | 178 - .../auth-compat-exp/demo/public/web-worker.js | 210 - packages-exp/auth-compat-exp/rollup.config.js | 21 - .../auth-compat-exp/rollup.config.release.js | 26 - .../auth-compat-exp/rollup.config.shared.js | 153 - packages-exp/auth-exp/README.md | 56 - packages-exp/auth-exp/demo/.gitignore | 6 - packages-exp/auth-exp/demo/README.md | 60 - .../auth-exp/demo/database.rules.json | 13 - packages-exp/auth-exp/demo/firebase.json | 14 - packages-exp/auth-exp/demo/functions/index.js | 45 - .../auth-exp/demo/functions/package.json | 19 - .../auth-exp/demo/functions/yarn.lock | 1759 -- packages-exp/auth-exp/demo/public/common.js | 109 - packages-exp/auth-exp/demo/public/index.html | 763 - .../auth-exp/demo/public/manifest.json | 13 - packages-exp/auth-exp/demo/public/style.css | 207 - packages-exp/auth-exp/package.json | 88 - .../auth-exp/rollup.config.release.js | 20 - packages-exp/auth-exp/rollup.config.shared.js | 203 - packages-exp/firebase-exp/.gitignore | 4 - packages-exp/firebase-exp/README.md | 5 - packages-exp/firebase-exp/analytics/index.ts | 18 - .../firebase-exp/analytics/package.json | 8 - .../firebase-exp/app-check/package.json | 8 - packages-exp/firebase-exp/app/index.ts | 21 - packages-exp/firebase-exp/app/package.json | 8 - .../firebase-exp/auth/cordova/package.json | 8 - packages-exp/firebase-exp/auth/index.ts | 17 - packages-exp/firebase-exp/auth/package.json | 8 - .../auth/react-native/package.json | 8 - .../compat/analytics/package.json | 8 - .../compat/app-check/package.json | 8 - .../firebase-exp/compat/app/package.json | 8 - .../firebase-exp/compat/auth/package.json | 8 - .../firebase-exp/compat/database/package.json | 8 - .../compat/firestore/package.json | 8 - .../compat/functions/package.json | 8 - packages-exp/firebase-exp/compat/index.d.ts | 10091 ----------- .../compat/messaging/package.json | 8 - .../compat/performance/package.json | 8 - .../compat/remote-config/package.json | 8 - .../compat/rollup.config.release.js | 411 - .../firebase-exp/compat/storage/package.json | 8 - .../firebase-exp/database/package.json | 8 - .../firebase-exp/firestore/lite/package.json | 8 - .../firebase-exp/firestore/package.json | 8 - packages-exp/firebase-exp/functions/index.ts | 17 - .../firebase-exp/functions/package.json | 8 - packages-exp/firebase-exp/messaging/index.ts | 18 - .../firebase-exp/messaging/package.json | 8 - .../firebase-exp/messaging/sw/index.ts | 22 - .../firebase-exp/messaging/sw/package.json | 8 - packages-exp/firebase-exp/package.json | 277 - .../firebase-exp/performance/index.ts | 17 - .../firebase-exp/performance/package.json | 8 - .../firebase-exp/remote-config/index.ts | 17 - .../firebase-exp/remote-config/package.json | 8 - packages-exp/firebase-exp/rollup.config.js | 132 - .../firebase-exp/rollup.config.release.js | 154 - packages-exp/firebase-exp/storage/index.ts | 18 - .../firebase-exp/storage/package.json | 8 - .../functions-compat/rollup.config.base.js | 101 - .../functions-compat/rollup.config.js | 21 - .../functions-compat/rollup.config.release.js | 25 - packages-exp/functions-exp/package.json | 68 - packages-exp/functions-exp/rollup.config.js | 58 - .../functions-exp/rollup.config.release.js | 73 - packages-exp/functions-exp/rollup.shared.js | 59 - packages-exp/functions-exp/src/config.ts | 66 - packages-exp/functions-exp/src/context.ts | 140 - packages-exp/functions-exp/src/serializer.ts | 109 - packages-exp/functions-exp/test/utils.ts | 81 - packages-exp/functions-exp/tsconfig.json | 9 - .../installations-compat/rollup.config.js | 53 - .../rollup.config.release.js | 71 - packages-exp/installations-exp/karma.conf.js | 35 - packages-exp/installations-exp/package.json | 64 - .../installations-exp/rollup.config.js | 53 - .../rollup.config.release.js | 71 - .../installations-exp/rollup.shared.js | 51 - .../helpers/buffer-to-base64-url-safe.test.ts | 36 - .../src/helpers/buffer-to-base64-url-safe.ts | 21 - .../src/helpers/extract-app-config.test.ts | 60 - .../src/helpers/extract-app-config.ts | 57 - .../src/helpers/fid-changed.test.ts | 103 - .../src/helpers/fid-changed.ts | 110 - .../src/helpers/generate-fid.test.ts | 128 - .../src/helpers/generate-fid.ts | 55 - .../helpers/get-installation-entry.test.ts | 477 - .../src/helpers/get-installation-entry.ts | 227 - .../src/helpers/idb-manager.test.ts | 194 - .../src/helpers/idb-manager.ts | 123 - .../src/helpers/refresh-auth-token.test.ts | 208 - .../src/helpers/refresh-auth-token.ts | 214 - packages-exp/installations-exp/src/index.ts | 32 - .../src/interfaces/api-response.ts | 34 - .../src/interfaces/installation-entry.ts | 110 - .../src/testing/compare-headers.test.ts | 44 - .../src/testing/compare-headers.ts | 41 - .../src/testing/fake-generators.ts | 70 - .../installations-exp/src/testing/setup.ts | 30 - .../installations-exp/src/util/constants.ts | 31 - .../installations-exp/src/util/errors.ts | 72 - .../installations-exp/src/util/get-key.ts | 23 - .../installations-exp/src/util/sleep.test.ts | 37 - .../installations-exp/src/util/sleep.ts | 23 - .../installations-exp/test-app/.gitignore | 2 - .../installations-exp/test-app/index.html | 43 - .../installations-exp/test-app/index.js | 113 - .../test-app/rollup.config.js | 48 - packages-exp/installations-exp/tsconfig.json | 14 - packages-exp/messaging-exp/.eslintrc.js | 26 - packages-exp/messaging-exp/README.md | 5 - packages-exp/messaging-exp/karma.conf.js | 32 - packages-exp/messaging-exp/package.json | 61 - .../messaging-exp/rollup.config.release.js | 102 - .../helpers/array-base64-translator.test.ts | 84 - .../src/helpers/array-base64-translator.ts | 37 - .../src/helpers/externalizePayload.test.ts | 115 - .../src/helpers/externalizePayload.ts | 96 - .../src/helpers/extract-app-config.test.ts | 80 - .../src/helpers/extract-app-config.ts | 61 - .../src/helpers/is-console-message.ts | 24 - .../src/helpers/migrate-old-database.test.ts | 204 - .../src/helpers/migrate-old-database.ts | 193 - .../messaging-exp/src/helpers/sleep.test.ts | 39 - .../messaging-exp/src/helpers/sleep.ts | 23 - packages-exp/messaging-exp/src/index.ts | 44 - .../src/interfaces/app-config.ts | 25 - .../src/interfaces/internal-dependencies.ts | 29 - .../interfaces/internal-message-payload.ts | 66 - .../src/interfaces/token-details.ts | 34 - .../src/testing/compare-headers.test.ts | 46 - .../src/testing/compare-headers.ts | 40 - .../testing/fakes/firebase-dependencies.ts | 78 - .../src/testing/fakes/service-worker.ts | 219 - .../src/testing/fakes/token-details.ts | 36 - .../messaging-exp/src/testing/setup.ts | 33 - .../messaging-exp/src/testing/sinon-types.ts | 30 - .../messaging-exp/src/util/constants.ts | 51 - packages-exp/messaging-exp/src/util/errors.ts | 96 - .../messaging-exp/src/util/sw-types.ts | 114 - packages-exp/messaging-exp/tsconfig.json | 15 - packages-exp/performance-compat/.eslintrc.js | 26 - .../performance-compat/rollup.config.js | 60 - .../rollup.config.release.js | 67 - packages-exp/performance-exp/README.md | 5 - packages-exp/performance-exp/karma.conf.js | 34 - packages-exp/performance-exp/package.json | 64 - packages-exp/performance-exp/rollup.config.js | 58 - .../performance-exp/rollup.config.release.js | 71 - packages-exp/performance-exp/rollup.shared.js | 48 - packages-exp/performance-exp/src/constants.ts | 42 - .../src/controllers/perf.test.ts | 151 - .../performance-exp/src/controllers/perf.ts | 94 - .../src/resources/network_request.test.ts | 92 - .../src/resources/network_request.ts | 83 - .../src/resources/trace.test.ts | 293 - .../performance-exp/src/resources/trace.ts | 356 - .../src/services/api_service.test.ts | 114 - .../src/services/api_service.ts | 162 - .../src/services/iid_service.test.ts | 60 - .../src/services/iid_service.ts | 52 - .../services/initialization_service.test.ts | 85 - .../src/services/initialization_service.ts | 83 - .../services/oob_resources_service.test.ts | 226 - .../src/services/oob_resources_service.ts | 121 - .../src/services/perf_logger.test.ts | 435 - .../src/services/perf_logger.ts | 250 - .../services/remote_config_service.test.ts | 284 - .../src/services/remote_config_service.ts | 240 - .../src/services/settings_service.ts | 67 - .../src/services/transport_service.test.ts | 183 - .../src/services/transport_service.ts | 192 - .../src/utils/attribute_utils.test.ts | 215 - .../src/utils/attributes_utils.ts | 117 - .../src/utils/console_logger.ts | 22 - .../performance-exp/src/utils/errors.ts | 84 - .../src/utils/metric_utils.test.ts | 93 - .../performance-exp/src/utils/metric_utils.ts | 64 - .../src/utils/string_merger.test.ts | 52 - .../src/utils/string_merger.ts | 35 - packages-exp/performance-exp/tsconfig.json | 12 - .../remote-config-compat/.eslintrc.js | 26 - .../remote-config-compat/rollup.config.js | 60 - .../rollup.config.release.js | 67 - .../remote-config-compat/rollup.shared.js | 52 - .../remote-config-compat/test/setup.ts | 26 - packages-exp/remote-config-exp/.eslintrc.js | 26 - packages-exp/remote-config-exp/.npmignore | 1 - packages-exp/remote-config-exp/README.md | 27 - packages-exp/remote-config-exp/package.json | 65 - .../remote-config-exp/rollup.config.js | 58 - .../rollup.config.release.js | 73 - .../remote-config-exp/rollup.shared.js | 54 - .../src/client/caching_client.ts | 123 - .../src/client/remote_config_fetch_client.ts | 139 - .../src/client/rest_client.ts | 176 - .../src/client/retrying_client.ts | 144 - .../remote-config-exp/src/constants.ts | 18 - packages-exp/remote-config-exp/src/errors.ts | 97 - .../remote-config-exp/src/language.ts | 38 - .../remote-config-exp/src/remote_config.ts | 88 - .../remote-config-exp/src/storage/storage.ts | 260 - .../src/storage/storage_cache.ts | 99 - packages-exp/remote-config-exp/src/value.ts | 57 - .../test/client/caching_client.test.ts | 160 - .../test/client/rest_client.test.ts | 270 - .../test/client/retrying_client.test.ts | 218 - .../remote-config-exp/test/errors.test.ts | 31 - .../remote-config-exp/test/language.test.ts | 44 - .../test/remote_config.test.ts | 520 - packages-exp/remote-config-exp/test/setup.ts | 26 - .../test/storage/storage.test.ts | 119 - .../test/storage/storage_cache.test.ts | 84 - .../remote-config-exp/test/value.test.ts | 78 - .../remote-config-exp/test_app/index.html | 154 - .../remote-config-exp/test_app/index.js | 224 - packages-exp/remote-config-exp/tsconfig.json | 10 - .../analytics-compat/.eslintrc.js | 0 .../analytics-compat}/README.md | 4 +- .../analytics-compat/karma.conf.js | 0 .../analytics-compat/package.json | 9 +- .../analytics-compat}/rollup.config.js | 32 +- .../analytics-compat/src/constants.ts | 0 .../analytics-compat/src/index.ts | 10 +- .../analytics-compat/src/service.test.ts | 2 +- .../analytics-compat/src/service.ts | 2 +- .../analytics-compat/tsconfig.json | 0 packages/analytics-types/index.d.ts | 2 +- .../analytics}/api-extractor.json | 0 packages/analytics/index.test.ts | 371 - packages/analytics/index.ts | 139 - packages/analytics/package.json | 24 +- packages/analytics/rollup.config.js | 28 +- .../analytics}/src/api.test.ts | 2 +- .../analytics}/src/api.ts | 6 +- packages/analytics/src/constants.ts | 49 +- packages/analytics/src/errors.ts | 6 + packages/analytics/src/factory.ts | 125 +- packages/analytics/src/functions.test.ts | 44 +- packages/analytics/src/functions.ts | 4 +- packages/analytics/src/get-config.test.ts | 2 +- packages/analytics/src/get-config.ts | 8 +- packages/analytics/src/helpers.test.ts | 25 +- packages/analytics/src/helpers.ts | 35 +- .../analytics}/src/index.test.ts | 4 +- .../analytics}/src/index.ts | 8 +- .../src/initialize-analytics.test.ts | 4 +- .../analytics}/src/initialize-analytics.ts | 4 +- packages/analytics/src/initialize-ids.test.ts | 124 - packages/analytics/src/initialize-ids.ts | 145 - .../analytics}/src/public-types.ts | 2 +- .../analytics}/src/types.ts | 0 .../testing/get-fake-firebase-services.ts | 69 +- .../testing/integration-tests/integration.ts | 93 +- .../app-check-compat/.eslintrc.js | 0 .../app-check-compat/README.md | 0 .../app-check-compat/karma.conf.js | 0 .../app-check-compat/package.json | 7 +- .../app-check-compat/rollup.config.js | 41 +- .../app-check-compat/rollup.shared.js | 2 - .../app-check-compat/src/errors.ts | 0 .../app-check-compat/src/index.ts | 6 - .../app-check-compat/src/service.test.ts | 4 +- .../app-check-compat/src/service.ts | 2 +- .../app-check-compat}/tsconfig.json | 0 packages/app-check-types/index.d.ts | 2 +- packages/app-check/.eslintrc.js | 21 +- packages/app-check/README.md | 4 +- .../app-check}/api-extractor.json | 0 packages/app-check/karma.conf.js | 15 +- packages/app-check/package.json | 31 +- packages/app-check/rollup.config.js | 27 +- packages/app-check/src/api.test.ts | 317 +- packages/app-check/src/api.ts | 190 +- packages/app-check/src/client.test.ts | 2 +- packages/app-check/src/client.ts | 4 +- packages/app-check/src/errors.ts | 17 +- packages/app-check/src/factory.ts | 116 +- packages/app-check/src/index.ts | 86 +- packages/app-check/src/indexeddb.ts | 4 +- packages/app-check/src/internal-api.test.ts | 375 +- packages/app-check/src/internal-api.ts | 69 +- packages/app-check/src/providers.ts | 72 +- .../app-check}/src/public-types.ts | 4 +- packages/app-check/src/recaptcha.test.ts | 30 +- packages/app-check/src/recaptcha.ts | 2 +- packages/app-check/src/state.ts | 33 +- packages/app-check/src/storage.ts | 12 +- .../app-check}/src/types.ts | 2 +- packages/app-check/src/util.ts | 17 +- packages/app-check/test/util.ts | 69 +- packages/app-check/tsconfig.json | 9 +- .../app-compat/.eslintrc.js | 0 .../app-compat/README.md | 2 +- .../app-compat/karma.conf.js | 0 .../app-compat/package.json | 10 +- .../app-compat/rollup.config.js | 0 .../app-compat/src/errors.ts | 0 .../app-compat/src/firebaseApp.ts | 2 +- .../app-compat/src/firebaseNamespace.ts | 0 .../app-compat/src/firebaseNamespaceCore.ts | 4 +- .../app-compat/src/index.lite.ts | 0 .../app-compat/src/index.ts | 0 .../app-compat/src/lite/firebaseAppLite.ts | 2 +- .../src/lite/firebaseNamespaceLite.ts | 0 .../app-compat/src/logger.ts | 0 .../app-compat/src/public-types.ts | 0 .../app-compat/src/registerCoreComponents.ts | 2 +- .../app-compat/src/types.ts | 0 .../app-compat/test/firebaseAppCompat.test.ts | 2 +- .../app-compat/test/setup.ts | 0 .../app-compat/test/util.ts | 0 .../app-compat/tsconfig.json | 0 packages/app-types/index.d.ts | 1 - packages/app/.eslintrc.js | 17 + packages/app/README.md | 3 +- .../app}/api-extractor.json | 2 +- packages/app/index.lite.ts | 26 - packages/app/index.node.ts | 43 - packages/app/index.rn.ts | 44 - packages/app/index.ts | 74 - packages/app/karma.conf.js | 8 +- packages/app/package.json | 39 +- packages/app/rollup.config.js | 75 +- .../app-exp => packages/app}/src/api.test.ts | 2 +- .../app-exp => packages/app}/src/api.ts | 4 +- packages/app/src/constants.ts | 56 +- packages/app/src/errors.ts | 3 +- .../app}/src/firebaseApp.test.ts | 0 packages/app/src/firebaseApp.ts | 189 +- packages/app/src/firebaseNamespace.ts | 54 - packages/app/src/firebaseNamespaceCore.ts | 308 - .../app-exp => packages/app}/src/index.ts | 0 .../app}/src/internal.test.ts | 0 .../app-exp => packages/app}/src/internal.ts | 0 packages/app/src/lite/firebaseAppLite.ts | 152 - .../app/src/lite/firebaseNamespaceLite.ts | 60 - .../app}/src/platformLoggerService.test.ts | 0 packages/app/src/platformLoggerService.ts | 5 +- .../app}/src/public-types.ts | 4 +- packages/app/src/registerCoreComponents.ts | 20 +- .../app-exp => packages/app}/src/types.ts | 0 packages/app/test/clientLogger.test.ts | 107 - packages/app/test/firebaseApp.test.ts | 462 - packages/app/test/platformLogger.test.ts | 147 - .../app-exp => packages/app}/test/util.ts | 2 +- packages/app/tsconfig.json | 8 +- .../auth-compat}/.eslintrc.js | 0 .../auth-compat}/README.md | 2 +- .../auth-compat}/demo/.eslintignore | 0 .../auth-compat}/demo/.eslintrc.js | 0 .../auth-compat}/demo/.gitignore | 0 .../auth-compat}/demo/README.md | 10 +- .../auth-compat}/demo/database.rules.json | 0 .../auth-compat}/demo/firebase.json | 0 .../auth-compat}/demo/functions/index.js | 0 .../auth-compat}/demo/functions/package.json | 0 .../auth-compat}/demo/functions/yarn.lock | 0 .../auth-compat}/demo/package.json | 8 +- .../auth-compat}/demo/public/common.js | 0 .../auth-compat}/demo/public/manifest.json | 0 .../auth-compat}/demo/public/sample-config.js | 0 .../auth-compat}/demo/public/script.js | 0 .../auth-compat}/demo/public/style.css | 0 .../auth-compat}/demo/rollup.config.js | 6 +- .../auth-compat}/demo/tsconfig.json | 0 .../auth-compat}/demo/yarn.lock | 0 .../auth-compat}/index.node.ts | 2 +- .../auth-compat}/index.ts | 14 +- .../auth-compat}/karma.conf.js | 0 .../auth-compat}/package.json | 15 +- packages/auth-compat/rollup.config.js | 147 + .../auth-compat}/scripts/run_node_tests.ts | 4 +- .../auth-compat}/src/auth.test.ts | 8 +- .../auth-compat}/src/auth.ts | 5 +- .../auth-compat}/src/persistence.ts | 2 +- .../auth-compat}/src/phone_auth_provider.ts | 2 +- .../auth-compat}/src/platform.ts | 2 +- .../auth-compat}/src/popup_redirect.test.ts | 2 +- .../auth-compat}/src/popup_redirect.ts | 2 +- .../auth-compat}/src/recaptcha_verifier.ts | 2 +- .../auth-compat}/src/user.ts | 2 +- .../auth-compat}/src/user_credential.ts | 2 +- .../auth-compat}/src/wrap.ts | 0 .../auth-compat}/test/helpers/helpers.ts | 6 +- .../test/integration/flows/anonymous.test.ts | 0 .../test/integration/flows/custom.test.ts | 0 .../test/integration/flows/email.test.ts | 0 .../test/integration/flows/idp.test.ts | 0 .../test/integration/flows/oob.test.ts | 2 +- .../test/integration/flows/phone.test.ts | 2 +- .../integration/webdriver/static/anonymous.js | 0 .../test/integration/webdriver/static/core.js | 0 .../integration/webdriver/static/email.js | 0 .../integration/webdriver/static/index.html | 0 .../integration/webdriver/static/index.js | 7 - .../integration/webdriver/static/lazy_load.js | 0 .../webdriver/static/logged_in.html | 0 .../webdriver/static/persistence.js | 0 .../integration/webdriver/static/popup.js | 0 .../integration/webdriver/static/redirect.js | 0 .../webdriver/static/rollup.config.js | 2 +- .../test/integration/webdriver/static/ui.js | 0 .../auth-compat}/tsconfig.json | 0 packages/auth-types/index.d.ts | 2 +- .../auth-exp => packages/auth}/.eslintrc.js | 0 packages/auth/.gitignore | 19 - packages/auth/CONTRIBUTING.md | 86 - packages/auth/LICENSE | 202 - packages/auth/README.md | 51 + packages/auth/STYLEGUIDE.md | 34 - .../auth}/api-extractor.json | 5 +- packages/auth/buildtools/all_tests.html | 24 - packages/auth/buildtools/common.py | 44 - packages/auth/buildtools/gen_all_tests_js.py | 45 - packages/auth/buildtools/gen_test_html.py | 122 - .../auth/buildtools/generate_test_files.sh | 37 - packages/auth/buildtools/run_demo.sh | 37 - packages/auth/buildtools/run_tests.sh | 115 - packages/auth/buildtools/sauce_connect.sh | 63 - packages/auth/buildtools/test_template.html | 17 - .../auth}/cordova/demo/.gitignore | 0 .../auth}/cordova/demo/README.md | 0 .../auth}/cordova/demo/package.json | 6 +- .../auth}/cordova/demo/rollup.config.js | 0 .../auth}/cordova/demo/sample-config.xml | 0 .../auth}/cordova/demo/src/index.js | 4 +- .../auth}/cordova/demo/src/logging.js | 0 .../auth}/cordova/demo/src/sample-config.js | 0 .../auth}/cordova/demo/www/index.html | 0 .../auth}/cordova/demo/www/style.css | 0 .../auth}/cordova/package.json | 3 +- .../auth}/demo/.eslintignore | 0 .../auth}/demo/.eslintrc.js | 0 packages/auth/demo/.gitignore | 72 +- packages/auth/demo/README.md | 41 +- packages/auth/demo/firebase.json | 29 +- packages/auth/demo/functions/index.js | 20 +- packages/auth/demo/functions/package.json | 7 +- packages/auth/demo/functions/yarn.lock | 955 +- .../auth}/demo/package.json | 10 +- packages/auth/demo/public/common.js | 32 +- packages/auth/demo/public/index.html | 8 +- packages/auth/demo/public/sample-config.js | 26 - packages/auth/demo/public/script.js | 1822 -- packages/auth/demo/public/service-worker.js | 172 - packages/auth/demo/public/web-worker.js | 193 - .../auth}/demo/rollup.config.js | 0 .../auth}/demo/src/config.d.ts | 0 .../auth}/demo/src/index.js | 4 +- .../auth}/demo/src/logging.js | 0 .../auth}/demo/src/sample-config.js | 0 .../auth}/demo/src/worker/service-worker.ts | 4 +- .../auth}/demo/src/worker/tsconfig.json | 0 .../auth}/demo/src/worker/web-worker.ts | 4 +- .../auth}/demo/tsconfig.json | 0 packages/auth/externs/externs.js | 42 - packages/auth/externs/gapi.iframes.js | 503 - packages/auth/externs/grecaptcha.js | 68 - packages/auth/gulpfile.js | 120 - .../auth}/index.cordova.ts | 4 +- packages/auth/index.d.ts | 18 - .../auth-exp => packages/auth}/index.doc.ts | 6 + .../auth-exp => packages/auth}/index.node.ts | 0 .../auth-exp => packages/auth}/index.rn.ts | 4 +- .../auth}/index.shared.ts | 0 .../auth-exp => packages/auth}/index.ts | 0 .../auth}/index.webworker.ts | 2 +- .../auth}/internal/index.ts | 2 +- .../auth}/internal/package.json | 2 +- .../auth-exp => packages/auth}/karma.conf.js | 0 packages/auth/package.json | 89 +- packages/auth/protractor.conf.js | 115 - packages/auth/protractor_spec.js | 138 - .../auth}/react-native/package.json | 3 +- packages/auth/rollup.config.js | 172 + packages/auth/sauce_browsers.json | 43 - .../auth}/scripts/run_node_tests.ts | 0 packages/auth/src/actioncodeinfo.js | 140 - packages/auth/src/actioncodesettings.js | 251 - packages/auth/src/actioncodeurl.js | 124 - packages/auth/src/additionaluserinfo.js | 265 - .../api/account_management/account.test.ts | 0 .../src/api/account_management/account.ts | 0 .../email_and_password.test.ts | 0 .../account_management/email_and_password.ts | 0 .../src/api/account_management/mfa.test.ts | 0 .../auth}/src/api/account_management/mfa.ts | 0 .../api/account_management/profile.test.ts | 0 .../src/api/account_management/profile.ts | 0 .../authentication/create_auth_uri.test.ts | 0 .../src/api/authentication/create_auth_uri.ts | 0 .../api/authentication/custom_token.test.ts | 0 .../src/api/authentication/custom_token.ts | 0 .../authentication/email_and_password.test.ts | 0 .../api/authentication/email_and_password.ts | 0 .../src/api/authentication/email_link.test.ts | 0 .../src/api/authentication/email_link.ts | 0 .../auth}/src/api/authentication/idp.test.ts | 0 .../auth}/src/api/authentication/idp.ts | 0 .../auth}/src/api/authentication/mfa.test.ts | 0 .../auth}/src/api/authentication/mfa.ts | 0 .../src/api/authentication/recaptcha.test.ts | 0 .../auth}/src/api/authentication/recaptcha.ts | 0 .../src/api/authentication/sign_up.test.ts | 0 .../auth}/src/api/authentication/sign_up.ts | 0 .../auth}/src/api/authentication/sms.test.ts | 0 .../auth}/src/api/authentication/sms.ts | 0 .../src/api/authentication/token.test.ts | 2 +- .../auth}/src/api/authentication/token.ts | 0 .../auth}/src/api/errors.ts | 0 .../auth}/src/api/index.test.ts | 2 +- .../auth}/src/api/index.ts | 0 .../project_config/get_project_config.test.ts | 0 .../api/project_config/get_project_config.ts | 0 packages/auth/src/args.js | 650 - packages/auth/src/auth.js | 2258 --- packages/auth/src/authcredential.js | 1605 -- packages/auth/src/authevent.js | 210 - packages/auth/src/autheventmanager.js | 1229 -- packages/auth/src/authsettings.js | 72 - packages/auth/src/authstorage.js | 658 - packages/auth/src/authuser.js | 2556 --- packages/auth/src/cacherequest.js | 112 - packages/auth/src/confirmationresult.js | 101 - packages/auth/src/cordovahandler.js | 870 - .../auth}/src/core/action_code_url.test.ts | 0 .../auth}/src/core/action_code_url.ts | 0 .../src/core/auth/auth_event_manager.test.ts | 0 .../auth}/src/core/auth/auth_event_manager.ts | 0 .../auth}/src/core/auth/auth_impl.test.ts | 2 +- .../auth}/src/core/auth/auth_impl.ts | 2 +- .../auth}/src/core/auth/emulator.test.ts | 0 .../auth}/src/core/auth/emulator.ts | 0 .../src/core/auth/firebase_internal.test.ts | 0 .../auth}/src/core/auth/firebase_internal.ts | 0 .../auth}/src/core/auth/initialize.test.ts | 2 +- .../auth}/src/core/auth/initialize.ts | 4 +- .../auth}/src/core/auth/register.ts | 6 +- .../src/core/credentials/auth_credential.ts | 0 .../auth}/src/core/credentials/email.test.ts | 0 .../auth}/src/core/credentials/email.ts | 0 .../auth}/src/core/credentials/index.ts | 0 .../auth}/src/core/credentials/oauth.test.ts | 0 .../auth}/src/core/credentials/oauth.ts | 0 .../auth}/src/core/credentials/phone.test.ts | 0 .../auth}/src/core/credentials/phone.ts | 0 .../auth}/src/core/credentials/saml.test.ts | 0 .../auth}/src/core/credentials/saml.ts | 0 .../auth}/src/core/errors.test.ts | 0 .../auth}/src/core/errors.ts | 0 .../auth}/src/core/index.ts | 0 .../src/core/persistence/in_memory.test.ts | 0 .../auth}/src/core/persistence/in_memory.ts | 0 .../auth}/src/core/persistence/index.ts | 0 .../persistence_user_manager.test.ts | 0 .../persistence/persistence_user_manager.ts | 0 .../auth}/src/core/providers/email.test.ts | 0 .../auth}/src/core/providers/email.ts | 0 .../auth}/src/core/providers/facebook.test.ts | 0 .../auth}/src/core/providers/facebook.ts | 0 .../src/core/providers/federated.test.ts | 0 .../auth}/src/core/providers/federated.ts | 0 .../auth}/src/core/providers/github.test.ts | 0 .../auth}/src/core/providers/github.ts | 0 .../auth}/src/core/providers/google.test.ts | 0 .../auth}/src/core/providers/google.ts | 0 .../auth}/src/core/providers/oauth.test.ts | 0 .../auth}/src/core/providers/oauth.ts | 0 .../auth}/src/core/providers/saml.test.ts | 0 .../auth}/src/core/providers/saml.ts | 0 .../auth}/src/core/providers/twitter.test.ts | 0 .../auth}/src/core/providers/twitter.ts | 0 .../abstract_popup_redirect_operation.test.ts | 0 .../abstract_popup_redirect_operation.ts | 0 .../strategies/action_code_settings.test.ts | 0 .../core/strategies/action_code_settings.ts | 0 .../src/core/strategies/anonymous.test.ts | 0 .../auth}/src/core/strategies/anonymous.ts | 0 .../src/core/strategies/credential.test.ts | 0 .../auth}/src/core/strategies/credential.ts | 0 .../src/core/strategies/custom_token.test.ts | 0 .../auth}/src/core/strategies/custom_token.ts | 0 .../auth}/src/core/strategies/email.test.ts | 0 .../auth}/src/core/strategies/email.ts | 0 .../strategies/email_and_password.test.ts | 0 .../src/core/strategies/email_and_password.ts | 0 .../src/core/strategies/email_link.test.ts | 0 .../auth}/src/core/strategies/email_link.ts | 0 .../auth}/src/core/strategies/idp.test.ts | 0 .../auth}/src/core/strategies/idp.ts | 0 .../src/core/strategies/redirect.test.ts | 0 .../auth}/src/core/strategies/redirect.ts | 0 .../auth}/src/core/user/account_info.test.ts | 0 .../auth}/src/core/user/account_info.ts | 0 .../core/user/additional_user_info.test.ts | 0 .../src/core/user/additional_user_info.ts | 0 .../src/core/user/id_token_result.test.ts | 0 .../auth}/src/core/user/id_token_result.ts | 0 .../auth}/src/core/user/invalidation.test.ts | 0 .../auth}/src/core/user/invalidation.ts | 0 .../auth}/src/core/user/link_unlink.test.ts | 0 .../auth}/src/core/user/link_unlink.ts | 0 .../src/core/user/proactive_refresh.test.ts | 0 .../auth}/src/core/user/proactive_refresh.ts | 0 .../src/core/user/reauthenticate.test.ts | 0 .../auth}/src/core/user/reauthenticate.ts | 0 .../auth}/src/core/user/reload.test.ts | 0 .../auth}/src/core/user/reload.ts | 0 .../auth}/src/core/user/token_manager.test.ts | 0 .../auth}/src/core/user/token_manager.ts | 0 .../core/user/user_credential_impl.test.ts | 0 .../src/core/user/user_credential_impl.ts | 0 .../auth}/src/core/user/user_impl.test.ts | 0 .../auth}/src/core/user/user_impl.ts | 0 .../auth}/src/core/user/user_metadata.ts | 0 .../auth}/src/core/util/assert.test.ts | 0 .../auth}/src/core/util/assert.ts | 0 .../auth}/src/core/util/browser.test.ts | 0 .../auth}/src/core/util/browser.ts | 0 .../auth}/src/core/util/delay.test.ts | 0 .../auth}/src/core/util/delay.ts | 0 .../auth}/src/core/util/emulator.test.ts | 0 .../auth}/src/core/util/emulator.ts | 0 .../auth}/src/core/util/event_id.test.ts | 0 .../auth}/src/core/util/event_id.ts | 0 .../auth}/src/core/util/fetch_provider.ts | 0 .../auth}/src/core/util/handler.ts | 2 +- .../auth}/src/core/util/instantiator.test.ts | 0 .../auth}/src/core/util/instantiator.ts | 0 .../auth}/src/core/util/location.ts | 0 .../auth}/src/core/util/log.ts | 4 +- .../auth}/src/core/util/navigator.ts | 0 .../auth}/src/core/util/providers.ts | 0 .../auth}/src/core/util/resolver.ts | 0 .../auth}/src/core/util/time.ts | 0 .../src/core/util/validate_origin.test.ts | 0 .../auth}/src/core/util/validate_origin.ts | 0 .../auth}/src/core/util/version.test.ts | 2 +- .../auth}/src/core/util/version.ts | 2 +- packages/auth/src/debug.js | 29 - packages/auth/src/defines.js | 205 - packages/auth/src/deprecation.js | 72 - packages/auth/src/dynamiclink.js | 259 - packages/auth/src/error_auth.js | 474 - packages/auth/src/error_invalidorigin.js | 82 - packages/auth/src/error_withcredential.js | 153 - packages/auth/src/exports_auth.js | 804 - packages/auth/src/exports_lib.js | 224 - packages/auth/src/exports_unreleased.js | 26 - packages/auth/src/externs.js | 42 - packages/auth/src/idp.js | 177 - packages/auth/src/idtoken.js | 254 - packages/auth/src/idtokenresult.js | 67 - packages/auth/src/iframeclient/ifchandler.js | 1079 -- .../auth/src/iframeclient/iframewrapper.js | 315 - .../auth-exp => packages/auth}/src/index.ts | 0 packages/auth/src/messagechannel/defines.js | 85 - .../auth/src/messagechannel/postmessager.js | 131 - packages/auth/src/messagechannel/receiver.js | 223 - packages/auth/src/messagechannel/sender.js | 241 - .../auth}/src/mfa/index.ts | 0 .../auth}/src/mfa/mfa_assertion.ts | 0 .../auth}/src/mfa/mfa_error.ts | 0 .../auth}/src/mfa/mfa_info.test.ts | 0 .../auth}/src/mfa/mfa_info.ts | 0 .../auth}/src/mfa/mfa_resolver.test.ts | 0 .../auth}/src/mfa/mfa_resolver.ts | 0 .../auth}/src/mfa/mfa_session.test.ts | 0 .../auth}/src/mfa/mfa_session.ts | 0 .../auth}/src/mfa/mfa_user.test.ts | 0 .../auth}/src/mfa/mfa_user.ts | 0 .../auth}/src/model/application_verifier.ts | 0 .../auth}/src/model/auth.ts | 0 .../auth}/src/model/enum_maps.ts | 0 .../auth}/src/model/enums.ts | 0 .../auth}/src/model/id_token.ts | 0 .../auth}/src/model/popup_redirect.ts | 0 .../auth}/src/model/public_types.ts | 9 +- .../auth}/src/model/user.ts | 0 packages/auth/src/multifactorassertion.js | 226 - .../auth/src/multifactorauthcredential.js | 94 - packages/auth/src/multifactorerror.js | 102 - packages/auth/src/multifactorgenerator.js | 49 - packages/auth/src/multifactorinfo.js | 214 - packages/auth/src/multifactorresolver.js | 146 - packages/auth/src/multifactorsession.js | 127 - packages/auth/src/multifactoruser.js | 240 - packages/auth/src/oauthhelperstate.js | 212 - packages/auth/src/oauthsigninhandler.js | 117 - packages/auth/src/object.js | 215 - .../auth}/src/platform_browser/auth.test.ts | 2 +- .../auth}/src/platform_browser/auth_window.ts | 0 .../platform_browser/iframe/gapi.iframes.ts | 0 .../src/platform_browser/iframe/gapi.test.ts | 0 .../auth}/src/platform_browser/iframe/gapi.ts | 0 .../platform_browser/iframe/iframe.test.ts | 2 +- .../src/platform_browser/iframe/iframe.ts | 2 +- .../auth}/src/platform_browser/index.ts | 4 +- .../src/platform_browser/load_js.test.ts | 0 .../auth}/src/platform_browser/load_js.ts | 0 .../platform_browser/messagechannel/index.ts | 0 .../messagechannel/promise.test.ts | 0 .../messagechannel/promise.ts | 0 .../messagechannel/receiver.test.ts | 0 .../messagechannel/receiver.ts | 0 .../messagechannel/sender.test.ts | 0 .../platform_browser/messagechannel/sender.ts | 0 .../mfa/assertions/phone.test.ts | 0 .../platform_browser/mfa/assertions/phone.ts | 0 .../platform_browser/persistence/browser.ts | 0 .../persistence/indexed_db.test.ts | 0 .../persistence/indexed_db.ts | 0 .../persistence/local_storage.test.ts | 0 .../persistence/local_storage.ts | 0 .../persistence/session_storage.test.ts | 0 .../persistence/session_storage.ts | 0 .../platform_browser/popup_redirect.test.ts | 2 +- .../src/platform_browser/popup_redirect.ts | 0 .../platform_browser/providers/phone.test.ts | 0 .../src/platform_browser/providers/phone.ts | 0 .../platform_browser/recaptcha/recaptcha.ts | 0 .../recaptcha/recaptcha_loader.test.ts | 0 .../recaptcha/recaptcha_loader.ts | 0 .../recaptcha/recaptcha_mock.test.ts | 0 .../recaptcha/recaptcha_mock.ts | 0 .../recaptcha/recaptcha_verifier.test.ts | 0 .../recaptcha/recaptcha_verifier.ts | 0 .../platform_browser/strategies/phone.test.ts | 0 .../src/platform_browser/strategies/phone.ts | 0 .../platform_browser/strategies/popup.test.ts | 0 .../src/platform_browser/strategies/popup.ts | 0 .../strategies/redirect.test.ts | 0 .../platform_browser/strategies/redirect.ts | 0 .../src/platform_browser/util/popup.test.ts | 0 .../auth}/src/platform_browser/util/popup.ts | 0 .../auth}/src/platform_browser/util/worker.ts | 0 .../auth}/src/platform_cordova/plugins.ts | 0 .../popup_redirect/events.test.ts | 0 .../platform_cordova/popup_redirect/events.ts | 0 .../popup_redirect/popup_redirect.test.ts | 0 .../popup_redirect/popup_redirect.ts | 0 .../popup_redirect/utils.test.ts | 0 .../platform_cordova/popup_redirect/utils.ts | 0 .../platform_cordova/strategies/redirect.ts | 0 .../auth}/src/platform_node/index.ts | 4 +- .../persistence/react_native.test.ts | 0 .../persistence/react_native.ts | 0 .../platform_react_native/react-native.d.ts | 0 packages/auth/src/proactiverefresh.js | 217 - .../auth/src/recaptchaverifier/grecaptcha.js | 70 - .../src/recaptchaverifier/grecaptchamock.js | 286 - packages/auth/src/recaptchaverifier/loader.js | 47 - .../auth/src/recaptchaverifier/mockloader.js | 81 - .../auth/src/recaptchaverifier/realloader.js | 177 - .../recaptchaverifier/recaptchaverifier.js | 509 - packages/auth/src/rpchandler.js | 3025 ---- packages/auth/src/storage/asyncstorage.js | 109 - packages/auth/src/storage/factory.js | 139 - packages/auth/src/storage/hybridindexeddb.js | 176 - packages/auth/src/storage/indexeddb.js | 733 - packages/auth/src/storage/inmemorystorage.js | 91 - packages/auth/src/storage/localstorage.js | 188 - packages/auth/src/storage/mockstorage.js | 102 - packages/auth/src/storage/nullstorage.js | 85 - packages/auth/src/storage/sessionstorage.js | 180 - packages/auth/src/storage/storage.js | 87 - packages/auth/src/storageautheventmanager.js | 133 - .../auth/src/storageoauthhandlermanager.js | 166 - .../auth/src/storagependingredirectmanager.js | 102 - .../auth/src/storageredirectusermanager.js | 103 - packages/auth/src/storageusermanager.js | 489 - packages/auth/src/token.js | 315 - packages/auth/src/universallinksubscriber.js | 109 - packages/auth/src/userevent.js | 67 - packages/auth/src/utils.js | 1528 -- packages/auth/test/actioncodeinfo_test.js | 317 - packages/auth/test/actioncodesettings_test.js | 346 - packages/auth/test/actioncodeurl_test.js | 149 - packages/auth/test/additionaluserinfo_test.js | 578 - packages/auth/test/args_test.js | 914 - packages/auth/test/auth_test.js | 11771 ------------ packages/auth/test/authcredential_test.js | 3625 ---- packages/auth/test/authevent_test.js | 335 - packages/auth/test/autheventmanager_test.js | 3338 ---- packages/auth/test/authsettings_test.js | 46 - packages/auth/test/authstorage_test.js | 1326 -- packages/auth/test/authuser_test.js | 15000 ---------------- packages/auth/test/cacherequest_test.js | 213 - packages/auth/test/confirmationresult_test.js | 203 - packages/auth/test/cordovahandler_test.js | 2950 --- packages/auth/test/defines_test.js | 91 - packages/auth/test/deprecation_test.js | 81 - packages/auth/test/dynamiclink_test.js | 361 - packages/auth/test/error_test.js | 671 - packages/auth/test/exports_lib_test.js | 276 - .../auth}/test/helpers/api/helper.ts | 0 .../auth}/test/helpers/delay.ts | 0 .../auth}/test/helpers/fake_service_worker.ts | 0 .../auth}/test/helpers/id_token_response.ts | 0 .../auth}/test/helpers/iframe_event.ts | 0 .../integration/emulator_rest_helpers.ts | 0 .../auth}/test/helpers/integration/helpers.ts | 2 +- .../test/helpers/integration/settings.ts | 2 +- .../auth}/test/helpers/jwt.ts | 0 .../auth}/test/helpers/mock_auth.ts | 2 +- .../test/helpers/mock_auth_credential.ts | 0 .../auth}/test/helpers/mock_fetch.test.ts | 0 .../auth}/test/helpers/mock_fetch.ts | 0 .../helpers/mock_popup_redirect_resolver.ts | 0 .../test/helpers/redirect_persistence.ts | 0 .../auth}/test/helpers/timeout_stub.ts | 0 packages/auth/test/idp_test.js | 96 - packages/auth/test/idtoken_test.js | 463 - packages/auth/test/idtokenresult_test.js | 217 - .../auth/test/iframeclient/ifchandler_test.js | 2042 --- .../test/iframeclient/iframewrapper_test.js | 515 - .../test/integration/flows/anonymous.test.ts | 2 +- .../integration/flows/custom.local.test.ts | 2 +- .../test/integration/flows/email.test.ts | 2 +- .../test/integration/flows/idp.local.test.ts | 2 +- .../test/integration/flows/oob.local.test.ts | 2 +- .../test/integration/flows/phone.test.ts | 2 +- .../integration/webdriver/anonymous.test.ts | 2 +- .../webdriver/compat/firebaseui.test.ts | 0 .../integration/webdriver/persistence.test.ts | 2 +- .../test/integration/webdriver/popup.test.ts | 2 +- .../integration/webdriver/redirect.test.ts | 2 +- .../integration/webdriver/static/anonymous.js | 2 +- .../test/integration/webdriver/static/core.js | 0 .../integration/webdriver/static/email.js | 2 +- .../integration/webdriver/static/index.html | 0 .../integration/webdriver/static/index.js | 4 +- .../webdriver/static/persistence.js | 2 +- .../integration/webdriver/static/popup.js | 2 +- .../integration/webdriver/static/redirect.js | 2 +- .../webdriver/static/rollup.config.js | 2 +- .../integration/webdriver/util/auth_driver.ts | 2 +- .../integration/webdriver/util/functions.ts | 0 .../integration/webdriver/util/idp_page.ts | 0 .../webdriver/util/js_load_condition.ts | 0 .../integration/webdriver/util/test_runner.ts | 0 .../integration/webdriver/util/test_server.ts | 2 +- .../integration/webdriver/util/ui_page.ts | 0 .../test/messagechannel/postmessager_test.js | 89 - .../auth/test/messagechannel/receiver_test.js | 479 - .../auth/test/messagechannel/sender_test.js | 660 - .../auth/test/multifactorassertion_test.js | 334 - packages/auth/test/multifactorerror_test.js | 229 - .../auth/test/multifactorgenerator_test.js | 76 - packages/auth/test/multifactorinfo_test.js | 161 - .../auth/test/multifactorresolver_test.js | 241 - packages/auth/test/multifactorsession_test.js | 157 - packages/auth/test/multifactoruser_test.js | 1192 -- packages/auth/test/oauthhelperstate_test.js | 383 - packages/auth/test/object_test.js | 318 - packages/auth/test/proactiverefresh_test.js | 495 - .../recaptchaverifier/grecaptchamock_test.js | 553 - .../test/recaptchaverifier/mockloader_test.js | 46 - .../test/recaptchaverifier/realloader_test.js | 349 - .../recaptchaverifier_test.js | 2152 --- packages/auth/test/rpchandler_test.js | 9982 ---------- .../auth/test/storage/asyncstorage_test.js | 62 - packages/auth/test/storage/factory_test.js | 214 - .../auth/test/storage/hybridindexeddb_test.js | 263 - packages/auth/test/storage/indexeddb_test.js | 829 - .../auth/test/storage/inmemorystorage_test.js | 50 - .../auth/test/storage/localstorage_test.js | 148 - .../auth/test/storage/mockstorage_test.js | 129 - .../auth/test/storage/nullstorage_test.js | 42 - .../auth/test/storage/sessionstorage_test.js | 116 - packages/auth/test/storage/testhelper.js | 153 - .../auth/test/storageautheventmanager_test.js | 203 - .../test/storageoauthhandlermanager_test.js | 192 - .../storagependingredirectmanager_test.js | 90 - .../test/storageredirectusermanager_test.js | 164 - packages/auth/test/storageusermanager_test.js | 754 - packages/auth/test/testhelper.js | 290 - packages/auth/test/token_test.js | 660 - .../auth/test/universallinksubscriber_test.js | 114 - packages/auth/test/userevent_test.js | 47 - packages/auth/test/utils_test.js | 1956 -- .../auth-exp => packages/auth}/tsconfig.json | 0 packages/component/src/provider.test.ts | 2 +- .../database-compat}/.eslintrc.js | 39 +- .../database-compat}/README.md | 4 +- .../database-compat}/karma.conf.js | 4 +- packages/database-compat/package.json | 37 + .../rollup.config.js} | 34 +- packages/database-compat/src/api/Database.ts | 123 + packages/database-compat/src/api/Reference.ts | 790 + .../src/api/TransactionResult.ts | 0 .../src/api/internal.ts | 65 +- .../src/api/onDisconnect.ts | 19 +- .../src}/index.node.ts | 24 +- .../compat => database-compat/src}/index.ts | 25 +- packages/database-compat/src/util/util.ts | 8 + .../database-compat/src/util/validation.ts | 42 + .../test/browser/crawler_support.test.ts | 2 +- .../test/database.test.ts | 4 +- .../test/datasnapshot.test.ts | 7 +- .../test/helpers/events.ts | 2 +- packages/database-compat/test/helpers/util.ts | 177 + .../test/info.test.ts | 2 +- .../test/order.test.ts | 2 +- .../test/order_by.test.ts | 2 +- .../test/promise.test.ts | 0 .../test/query.test.ts | 16 +- .../test/servervalues.test.ts | 0 .../test/transaction.test.ts | 13 +- .../database-compat}/tsconfig.json | 1 + packages/database-types/index.d.ts | 2 +- packages/database/.npmignore | 10 - packages/database/api-extractor.json | 8 +- packages/database/compat/package.json | 13 - packages/database/exp/package.json | 9 - packages/database/index.node.ts | 158 - packages/database/index.ts | 102 - packages/database/karma.conf.js | 2 - packages/database/package.json | 23 +- packages/database/rollup.config.compat.js | 144 - packages/database/rollup.config.js | 53 +- packages/database/{exp => src}/api.ts | 40 +- packages/database/src/api/Database.ts | 508 +- .../database/src/{exp => api}/OnDisconnect.ts | 0 packages/database/src/api/Reference.ts | 829 +- .../src/{exp => api}/Reference_impl.ts | 0 .../database/src/{exp => api}/ServerValue.ts | 0 .../database/src/{exp => api}/Transaction.ts | 0 packages/database/src/api/test_access.ts | 11 +- packages/database/src/core/SyncPoint.ts | 2 +- packages/database/src/core/SyncTree.ts | 9 +- .../database/src/core/snap/ChildrenNode.ts | 2 +- packages/database/src/core/snap/childSet.ts | 12 +- .../database/src/core/util/ImmutableTree.ts | 9 +- packages/database/src/core/util/SortedMap.ts | 4 +- .../database/src/core/util/libs/parser.ts | 4 +- packages/database/src/core/util/util.ts | 8 +- packages/database/src/core/util/validation.ts | 47 +- packages/database/src/core/version.ts | 5 +- packages/database/src/core/view/Event.ts | 2 +- .../src/core/view/EventRegistration.ts | 4 +- .../database/src/core/view/QueryParams.ts | 2 + .../database/src/core/view/ViewProcessor.ts | 13 +- packages/database/src/exp/Database.ts | 408 - packages/database/src/exp/Reference.ts | 135 - packages/database/{exp => src}/index.node.ts | 0 packages/database/{exp => src}/index.ts | 7 + .../src/realtime/BrowserPollConnection.ts | 31 +- packages/database/src/realtime/Constants.ts | 3 +- packages/database/{exp => src}/register.ts | 13 +- .../database/test/exp/integration.test.ts | 6 +- packages/database/test/helpers/util.ts | 176 +- .../firebase}/.eslintrc.js | 0 packages/firebase/README.md | 218 +- packages/firebase/analytics/index.ts | 4 +- packages/firebase/analytics/package.json | 5 +- packages/firebase/app-check/index.ts | 4 +- packages/firebase/app-check/package.json | 5 +- .../firebase}/app/index.cdn.ts | 4 +- packages/firebase/app/index.ts | 11 +- packages/firebase/app/package.json | 4 +- .../firebase/auth/cordova}/index.ts | 2 +- packages/firebase/auth/cordova/package.json | 7 + packages/firebase/auth/index.ts | 5 +- packages/firebase/auth/package.json | 5 +- .../firebase/auth/react-native}/index.ts | 2 +- .../firebase/auth/react-native/package.json | 7 + .../firebase}/compat/analytics/index.ts | 0 .../firebase/compat/analytics/package.json | 7 + .../firebase}/compat/app-check/index.ts | 0 .../firebase/compat/app-check/package.json | 7 + .../firebase}/compat/app/index.cdn.ts | 0 .../firebase}/compat/app/index.ts | 0 packages/firebase/compat/app/package.json | 7 + .../firebase}/compat/auth/index.ts | 0 packages/firebase/compat/auth/package.json | 7 + .../firebase}/compat/database/index.ts | 0 .../firebase/compat/database/package.json | 7 + .../firebase}/compat/firestore/index.ts | 0 .../firebase/compat/firestore/package.json | 7 + .../firebase}/compat/functions/index.ts | 0 .../firebase/compat/functions/package.json | 7 + .../firebase}/compat/index.cdn.ts | 0 packages/firebase/{ => compat}/index.d.ts | 0 .../firebase}/compat/index.node.ts | 0 .../firebase}/compat/index.perf.ts | 0 .../firebase}/compat/index.rn.ts | 0 .../firebase}/compat/index.ts | 0 .../firebase}/compat/messaging/index.ts | 0 .../firebase/compat/messaging/package.json | 7 + .../firebase}/compat/package.json | 7 +- .../firebase}/compat/performance/index.ts | 0 .../firebase/compat/performance/package.json | 7 + .../firebase}/compat/remote-config/index.ts | 0 .../compat/remote-config/package.json | 7 + .../firebase}/compat/rollup.config.js | 59 +- .../firebase}/compat/storage/index.ts | 0 packages/firebase/compat/storage/package.json | 7 + packages/firebase/database/index.ts | 4 +- packages/firebase/database/package.json | 5 +- packages/firebase/empty-import.d.ts | 20 - .../firebase/externs/firebase-app-externs.js | 293 - .../externs/firebase-app-internal-externs.js | 183 - .../firebase/externs/firebase-auth-externs.js | 3471 ---- .../externs/firebase-client-auth-externs.js | 502 - .../externs/firebase-database-externs.js | 1682 -- .../firebase-database-internal-externs.js | 27 - .../externs/firebase-error-externs.js | 73 - packages/firebase/externs/firebase-externs.js | 57 - .../externs/firebase-firestore-externs.js | 1383 -- .../externs/firebase-messaging-externs.js | 165 - .../externs/firebase-storage-externs.js | 670 - packages/firebase/firestore/bundle/index.ts | 18 - .../firebase/firestore/bundle/package.json | 7 - packages/firebase/firestore/index.cdn.ts | 19 - packages/firebase/firestore/index.ts | 4 +- .../firebase}/firestore/lite/index.ts | 0 packages/firebase/firestore/lite/package.json | 7 + .../firebase/firestore/memory/bundle/index.ts | 23 - .../firestore/memory/bundle/package.json | 7 - .../firebase/firestore/memory/index.cdn.ts | 24 - packages/firebase/firestore/memory/index.ts | 23 - .../firebase/firestore/memory/package.json | 7 - packages/firebase/firestore/package.json | 8 +- packages/firebase/functions/index.ts | 5 +- packages/firebase/functions/package.json | 5 +- .../firebase}/gulpfile.js | 10 +- packages/firebase/index.html | 1 - packages/firebase/installations/index.ts | 18 - packages/firebase/installations/package.json | 6 - packages/firebase/messaging/index.ts | 4 +- packages/firebase/messaging/package.json | 5 +- .../firebase/messaging/sw}/index.ts | 2 +- packages/firebase/messaging/sw/package.json | 7 + packages/firebase/package.json | 228 +- packages/firebase/performance/index.ts | 5 +- packages/firebase/performance/package.json | 5 +- packages/firebase/remote-config/index.ts | 5 +- packages/firebase/remote-config/package.json | 5 +- packages/firebase/rollup-internal.config.js | 39 - packages/firebase/rollup.config.js | 347 +- packages/firebase/src/index.cdn.ts | 47 - packages/firebase/src/index.node.ts | 30 - packages/firebase/src/index.perf.ts | 24 - packages/firebase/src/index.rn.ts | 31 - packages/firebase/src/index.ts | 56 - packages/firebase/storage/index.ts | 4 +- packages/firebase/storage/package.json | 5 +- packages/firebase/tsconfig.json | 6 +- packages/firestore-compat/.eslintrc.js | 76 + packages/firestore-compat/README.md | 24 + packages/firestore-compat/karma.conf.js | 83 + packages/firestore-compat/package.json | 46 + packages/firestore-compat/rollup.config.js | 99 + .../src/api/blob.ts | 11 +- packages/firestore-compat/src/api/database.ts | 1339 ++ .../firestore-compat/src/api/field_path.ts | 64 + .../firestore-compat/src/api/field_value.ts | 65 + .../src/api}/geo_point.ts | 4 +- packages/firestore-compat/src/api/observer.ts | 54 + .../src/api}/timestamp.ts | 4 +- .../compat => firestore-compat/src}/config.ts | 26 +- .../src}/index.console.ts | 33 +- .../src}/index.node.ts | 10 +- .../src}/index.rn.ts | 10 +- .../compat => firestore-compat/src}/index.ts | 12 +- .../src}/register-module.ts | 2 +- .../src/util/input_validation.ts | 32 +- .../tools/console.build.js | 64 +- .../tsconfig.json | 0 packages/firestore-types/index.d.ts | 2 +- packages/firestore/.eslintrc.js | 15 +- packages/firestore/.gitignore | 1 + .../All_Tests__Emulator_.xml | 4 +- ...l_Tests__Emulator_w__Mock_Persistence_.xml | 4 +- .../Integration_Tests__Emulator_.xml | 4 +- ...n_Tests__Emulator_w__Mock_Persistence_.xml | 4 +- .../.idea/runConfigurations/Unit_Tests.xml | 4 +- .../Unit_Tests__w__Mock_Persistence_.xml | 4 +- packages/firestore/api-extractor.json | 2 +- packages/firestore/bundle/package.json | 10 - packages/firestore/compat/bundle.ts | 55 - packages/firestore/compat/package.json | 15 - packages/firestore/exp/package.json | 12 - packages/firestore/export.ts | 69 - packages/firestore/externs.json | 8 +- packages/firestore/index.bundle.ts | 28 - packages/firestore/index.memory.ts | 43 - packages/firestore/index.node.memory.ts | 45 - packages/firestore/index.node.ts | 44 - packages/firestore/index.rn.memory.ts | 44 - packages/firestore/index.rn.ts | 47 - packages/firestore/index.ts | 48 - packages/firestore/lite/index.ts | 31 +- packages/firestore/lite/register.ts | 11 +- packages/firestore/memory-bundle/package.json | 10 - packages/firestore/memory/package.json | 11 - packages/firestore/package.json | 59 +- .../firestore/rollup.config.browser.compat.js | 69 - packages/firestore/rollup.config.browser.js | 120 - ...{rollup.config.exp.js => rollup.config.js} | 33 +- .../firestore/rollup.config.node.compat.js | 75 - packages/firestore/rollup.config.node.js | 113 - packages/firestore/rollup.config.rn.compat.js | 48 - packages/firestore/rollup.config.rn.js | 76 - packages/firestore/scripts/prepare-test.js | 18 + packages/firestore/scripts/prepare-test.ts | 84 + packages/firestore/{exp => src}/api.ts | 59 +- packages/firestore/src/{exp => api}/bundle.ts | 0 packages/firestore/src/{exp => api}/bytes.ts | 2 +- packages/firestore/src/api/credentials.ts | 180 +- packages/firestore/src/api/database.ts | 1668 +- packages/firestore/src/api/field_path.ts | 49 +- packages/firestore/src/api/field_value.ts | 52 +- .../src/{exp => api}/field_value_impl.ts | 2 +- packages/firestore/src/api/geo_point.ts | 4 +- packages/firestore/src/{exp => api}/query.ts | 2 +- .../firestore/src/{exp => api}/reference.ts | 2 +- .../src/{exp => api}/reference_impl.ts | 14 +- .../firestore/src/{exp => api}/settings.ts | 4 +- .../firestore/src/{exp => api}/snapshot.ts | 10 +- packages/firestore/src/api/timestamp.ts | 4 +- .../firestore/src/{exp => api}/transaction.ts | 6 +- .../firestore/src/{exp => api}/write_batch.ts | 2 +- packages/firestore/src/config.ts | 85 - .../firestore/src/core/component_provider.ts | 2 +- packages/firestore/src/core/database_info.ts | 5 +- .../firestore/src/core/firestore_client.ts | 12 +- .../firestore/src/core/snapshot_version.ts | 2 +- .../firestore/src/core/sync_engine_impl.ts | 2 +- packages/firestore/src/core/transaction.ts | 2 +- packages/firestore/src/exp/database.ts | 546 - packages/firestore/src/exp/field_path.ts | 18 - packages/firestore/src/exp/field_value.ts | 18 - packages/firestore/{exp => src}/index.node.ts | 0 packages/firestore/{exp => src}/index.rn.ts | 0 packages/firestore/{exp => src}/index.ts | 7 + .../firestore/src/{lite => lite-api}/bytes.ts | 0 .../src/{lite => lite-api}/components.ts | 2 +- .../src/{lite => lite-api}/database.ts | 12 +- .../src/{lite => lite-api}/field_path.ts | 0 .../src/{lite => lite-api}/field_value.ts | 0 .../{lite => lite-api}/field_value_impl.ts | 0 .../src/{lite => lite-api}/geo_point.ts | 0 .../firestore/src/{lite => lite-api}/query.ts | 0 .../src/{lite => lite-api}/reference.ts | 0 .../src/{lite => lite-api}/reference_impl.ts | 0 .../src/{lite => lite-api}/settings.ts | 0 .../src/{lite => lite-api}/snapshot.ts | 0 .../src/{lite => lite-api}/timestamp.ts | 0 .../src/{lite => lite-api}/transaction.ts | 0 .../firestore/src/{lite => lite-api}/types.ts | 0 .../{lite => lite-api}/user_data_reader.ts | 0 .../{lite => lite-api}/user_data_writer.ts | 5 +- .../src/{lite => lite-api}/write_batch.ts | 0 .../src/local/indexeddb_mutation_queue.ts | 2 +- .../src/local/indexeddb_target_cache.ts | 2 +- .../firestore/src/local/local_serializer.ts | 2 +- .../firestore/src/local/local_store_impl.ts | 2 +- .../src/local/memory_mutation_queue.ts | 2 +- .../firestore/src/local/mutation_queue.ts | 2 +- packages/firestore/src/model/document_key.ts | 3 + packages/firestore/src/model/mutation.ts | 2 +- .../firestore/src/model/mutation_batch.ts | 2 +- packages/firestore/src/model/path.ts | 7 +- .../firestore/src/model/server_timestamps.ts | 2 +- .../src/model/transform_operation.ts | 2 +- packages/firestore/src/platform/base64.ts | 5 +- packages/firestore/{exp => src}/register.ts | 20 +- packages/firestore/src/remote/serializer.ts | 2 +- packages/firestore/src/util/assert.ts | 2 + packages/firestore/src/util/byte_stream.ts | 9 +- packages/firestore/src/util/byte_string.ts | 1 + .../firestore/src/util/input_validation.ts | 25 +- packages/firestore/src/util/log.ts | 3 + .../test/integration/api/database.test.ts | 31 - .../firestore/test/integration/bootstrap.ts | 3 +- .../test/integration/util/firebase_export.ts | 19 +- .../test/integration/util/internal_helpers.ts | 9 +- packages/firestore/test/lite/helpers.ts | 12 +- .../firestore/test/lite/integration.test.ts | 26 +- packages/firestore/test/register.ts | 25 + packages/firestore/test/unit/api/blob.test.ts | 2 +- .../test/unit/api/document_change.test.ts | 6 +- .../test/unit/api/field_value.test.ts | 2 +- .../firestore/test/unit/core/query.test.ts | 2 +- .../test/unit/local/local_store.test.ts | 2 +- .../test/unit/model/mutation.test.ts | 2 +- .../test/unit/remote/serializer.helper.ts | 11 +- .../firestore/test/unit/specs/spec_builder.ts | 2 +- .../test/unit/specs/spec_test_runner.ts | 2 +- packages/firestore/test/util/api_helpers.ts | 32 +- packages/firestore/test/util/helpers.ts | 6 +- .../functions-compat/.eslintrc.js | 0 .../functions-compat/karma.conf.js | 0 .../functions-compat/package.json | 8 +- packages/functions-compat/rollup.config.js | 88 + .../functions-compat/src/callable.test.ts | 6 +- .../functions-compat/src/index.node.ts | 0 .../functions-compat/src/index.ts | 0 .../functions-compat/src/register.ts | 14 +- .../functions-compat/src/service.test.ts | 2 +- .../functions-compat/src/service.ts | 2 +- .../functions-compat/test/utils.ts | 2 +- .../functions-compat}/tsconfig.json | 0 packages/functions-types/index.d.ts | 2 +- packages/functions/.eslintrc.js | 28 + packages/functions/.npmignore | 9 - .../functions}/api-extractor.json | 0 packages/functions/index.node.ts | 26 - packages/functions/index.ts | 37 - packages/functions/karma.conf.js | 2 +- packages/functions/package.json | 34 +- packages/functions/rollup.config.js | 33 +- .../functions}/src/api.ts | 4 +- packages/functions/src/api/error.ts | 176 - packages/functions/src/api/service.ts | 338 - .../functions}/src/callable.test.ts | 2 +- packages/functions/src/config.ts | 56 +- .../functions}/src/constants.ts | 2 +- packages/functions/src/context.ts | 64 +- .../functions}/src/error.ts | 0 .../functions}/src/index.node.ts | 2 +- .../functions}/src/index.ts | 2 +- .../functions}/src/public-types.ts | 4 +- .../functions}/src/serializer.test.ts | 0 packages/functions/src/serializer.ts | 129 +- .../functions}/src/service.test.ts | 0 .../functions}/src/service.ts | 2 +- .../functions/test/browser/callable.test.ts | 79 - packages/functions/test/callable.test.ts | 231 - packages/functions/test/serializer.test.ts | 178 - packages/functions/test/service.test.ts | 91 - packages/functions/test/utils.ts | 148 +- .../installations-compat}/.eslintrc.js | 0 packages/installations-compat/README.md | 5 + .../installations-compat/karma.conf.js | 0 .../installations-compat/package.json | 5 +- .../installations-compat/rollup.config.js | 30 +- .../installations-compat/src/index.ts | 2 +- .../src/installationsCompat.test.ts | 2 +- .../src/installationsCompat.ts | 2 +- .../installations-compat/src/testing/setup.ts | 0 .../installations-compat/src/testing/util.ts | 4 +- .../installations-compat/tsconfig.json | 0 packages/installations-types/index.d.ts | 2 +- packages/installations/.eslintrc.js | 17 + .../installations}/api-extractor.json | 0 packages/installations/karma.conf.js | 11 +- packages/installations/package.json | 38 +- packages/installations/rollup.config.js | 18 +- packages/installations/src/api/common.test.ts | 74 - packages/installations/src/api/common.ts | 111 - .../api/create-installation-request.test.ts | 165 - .../src/api/create-installation-request.ts | 67 - .../api/delete-installation-request.test.ts | 123 - .../src/api/delete-installation-request.ts | 50 - .../src/api/delete-installations.test.ts | 0 .../src/api/delete-installations.ts | 0 .../api/generate-auth-token-request.test.ts | 150 - .../src/api/generate-auth-token-request.ts | 79 - .../installations}/src/api/get-id.test.ts | 0 .../installations}/src/api/get-id.ts | 0 .../src/api/get-installations.ts | 4 +- .../installations}/src/api/get-token.test.ts | 0 .../installations}/src/api/get-token.ts | 0 .../installations}/src/api/index.ts | 0 .../src/api/on-id-change.test.ts | 0 .../installations}/src/api/on-id-change.ts | 0 .../src/functions/common.test.ts | 0 .../installations}/src/functions/common.ts | 0 .../installations}/src/functions/config.ts | 14 +- .../create-installation-request.test.ts | 0 .../functions/create-installation-request.ts | 0 .../delete-installation-request.test.ts | 0 .../functions/delete-installation-request.ts | 0 .../src/functions/delete-installation.test.ts | 129 - .../src/functions/delete-installation.ts | 50 - .../generate-auth-token-request.test.ts | 0 .../functions/generate-auth-token-request.ts | 0 .../src/functions/get-id.test.ts | 89 - .../installations/src/functions/get-id.ts | 38 - .../src/functions/get-token.test.ts | 465 - .../installations/src/functions/get-token.ts | 44 - packages/installations/src/functions/index.ts | 21 - .../src/functions/on-id-change.test.ts | 52 - .../src/functions/on-id-change.ts | 37 - .../src/helpers/extract-app-config.test.ts | 8 +- .../src/helpers/extract-app-config.ts | 4 +- .../src/helpers/fid-changed.test.ts | 2 +- .../installations/src/helpers/fid-changed.ts | 4 +- .../helpers/get-installation-entry.test.ts | 4 +- .../src/helpers/get-installation-entry.ts | 4 +- .../src/helpers/idb-manager.test.ts | 2 +- .../installations/src/helpers/idb-manager.ts | 2 +- .../src/helpers/refresh-auth-token.test.ts | 50 +- .../src/helpers/refresh-auth-token.ts | 34 +- packages/installations/src/index.ts | 77 +- .../src/interfaces/app-config.ts | 23 - .../src/interfaces/firebase-dependencies.ts | 24 - .../src/interfaces/installation-impl.ts | 2 +- .../src/interfaces/public-types.ts | 13 +- .../src/testing/fake-generators.ts | 22 +- packages/installations/src/util/get-key.ts | 2 +- packages/installations/src/util/sleep.test.ts | 1 + packages/installations/src/util/sleep.ts | 2 +- packages/installations/test-app/index.html | 6 +- packages/installations/test-app/index.js | 4 +- .../installations/test-app/rollup.config.js | 2 +- packages/installations/tsconfig.json | 4 +- .../messaging-compat/.eslintrc.js | 0 .../messaging-compat/README.md | 0 .../messaging-compat/karma.conf.js | 0 .../messaging-compat/package.json | 8 +- .../messaging-compat/rollup.config.js | 0 .../messaging-compat/src/index.ts | 0 .../messaging-compat/src/messaging-compat.ts | 4 +- .../src/registerMessagingCompat.ts | 10 +- .../messaging-compat/test/fakes.ts | 2 +- .../test/messaging-compat.test.ts | 4 +- .../messaging-compat/tsconfig.json | 0 packages/messaging-types/index.d.ts | 4 +- packages/messaging/.eslintrc.js | 17 + packages/messaging/.npmignore | 9 - .../messaging}/api-extractor.json | 0 packages/messaging/package.json | 36 +- packages/messaging/rollup.config.js | 16 +- .../messaging}/src/api.ts | 43 +- .../messaging}/src/api/deleteToken.ts | 0 .../messaging}/src/api/getToken.ts | 0 .../messaging}/src/api/isSupported.ts | 0 .../messaging}/src/api/onBackgroundMessage.ts | 0 .../messaging}/src/api/onMessage.ts | 0 ...eliveryMetricsExportedToBigQueryEnabled.ts | 0 .../src/controllers/sw-controller.test.ts | 608 - .../src/controllers/sw-controller.ts | 411 - .../src/controllers/window-controller.test.ts | 659 - .../src/controllers/window-controller.ts | 298 - packages/messaging/src/core/api.test.ts | 217 - packages/messaging/src/core/api.ts | 186 - .../src/core/token-management.test.ts | 296 - .../messaging/src/core/token-management.ts | 184 - .../src/helpers/externalizePayload.test.ts | 14 +- .../src/helpers/externalizePayload.ts | 2 +- .../src/helpers/extract-app-config.test.ts | 4 +- .../src/helpers/extract-app-config.ts | 2 +- .../messaging/src/helpers/idb-manager.test.ts | 124 - packages/messaging/src/helpers/idb-manager.ts | 106 - .../src/helpers/logToFirelog.test.ts | 0 .../messaging}/src/helpers/logToFirelog.ts | 0 .../messaging}/src/helpers/logToScion.ts | 0 .../messaging}/src/helpers/register.ts | 56 +- .../src/helpers/registerDefaultSw.ts | 0 .../messaging}/src/helpers/updateSwReg.ts | 0 .../messaging}/src/helpers/updateVapidKey.ts | 0 .../messaging}/src/index.sw.ts | 5 +- packages/messaging/src/index.ts | 134 +- .../src/interfaces/internal-dependencies.ts | 6 +- .../src/interfaces/logging-types.ts | 0 .../messaging}/src/interfaces/public-types.ts | 11 +- .../src/internals/idb-manager.test.ts | 0 .../messaging}/src/internals/idb-manager.ts | 0 .../messaging}/src/internals/requests.test.ts | 0 .../messaging}/src/internals/requests.ts | 0 .../src/internals/token-manager.test.ts | 0 .../messaging}/src/internals/token-manager.ts | 0 .../src/listeners/sw-listeners.test.ts | 0 .../messaging}/src/listeners/sw-listeners.ts | 0 .../src/listeners/window-listener.ts | 0 .../messaging}/src/messaging-service.ts | 4 +- .../src/testing/compare-headers.test.ts | 2 +- .../messaging/src/testing/compare-headers.ts | 2 +- .../testing/fakes/firebase-dependencies.ts | 16 +- .../src/testing/fakes/logging-object.ts | 0 .../src/testing/fakes/messaging-service.ts | 0 .../src/testing/fakes/service-worker.ts | 27 +- packages/messaging/src/testing/setup.ts | 2 +- packages/messaging/src/util/constants.ts | 32 +- packages/messaging/src/util/errors.ts | 3 + packages/messaging/src/util/sw-types.ts | 38 +- .../messaging}/sw/package.json | 2 +- .../performance-compat}/.eslintrc.js | 0 .../performance-compat/README.md | 0 .../performance-compat/karma.conf.js | 0 .../performance-compat/package.json | 7 +- .../performance-compat}/rollup.config.js | 43 +- .../performance-compat/src/index.ts | 9 +- .../src/performance.test.ts | 2 +- .../performance-compat/src/performance.ts | 2 +- .../performance-compat}/test/setup.ts | 0 .../performance-compat/test/util.ts | 2 +- .../performance-compat/tsconfig.json | 0 packages/performance-types/index.d.ts | 2 +- packages/performance/.eslintrc.js | 17 + .../performance}/api-extractor.json | 0 packages/performance/index.ts | 83 - packages/performance/karma.conf.js | 2 +- packages/performance/package.json | 24 +- packages/performance/rollup.config.js | 24 +- packages/performance/src/constants.ts | 2 +- .../performance/src/controllers/perf.test.ts | 170 +- packages/performance/src/controllers/perf.ts | 57 +- .../performance}/src/index.test.ts | 2 +- .../performance}/src/index.ts | 16 +- .../performance}/src/public_types.ts | 9 +- .../src/resources/network_request.test.ts | 20 +- .../src/resources/network_request.ts | 10 +- .../performance/src/resources/trace.test.ts | 33 +- packages/performance/src/resources/trace.ts | 26 +- .../src/services/api_service.test.ts | 3 +- .../performance/src/services/api_service.ts | 9 +- .../src/services/iid_service.test.ts | 12 +- .../performance/src/services/iid_service.ts | 17 +- .../services/initialization_service.test.ts | 38 +- .../src/services/initialization_service.ts | 18 +- .../services/oob_resources_service.test.ts | 53 +- .../src/services/oob_resources_service.ts | 62 +- .../src/services/perf_logger.test.ts | 50 +- .../performance/src/services/perf_logger.ts | 17 +- .../services/remote_config_service.test.ts | 34 +- .../src/services/remote_config_service.ts | 21 +- .../src/services/settings_service.ts | 40 - .../src/services/transport_service.test.ts | 2 +- .../src/services/transport_service.ts | 2 +- .../performance}/src/utils/app_utils.ts | 2 +- .../src/utils/attribute_utils.test.ts | 2 +- .../performance/src/utils/attributes_utils.ts | 2 +- .../performance/src/utils/console_logger.ts | 2 +- packages/performance/src/utils/errors.ts | 12 +- .../src/utils/metric_utils.test.ts | 2 +- .../performance/src/utils/metric_utils.ts | 2 +- .../src/utils/string_merger.test.ts | 4 +- .../performance/src/utils/string_merger.ts | 2 +- packages/performance/test/setup.ts | 2 +- packages/polyfill/README.md | 7 - packages/polyfill/index.ts | 41 - packages/polyfill/package.json | 36 - packages/polyfill/rollup.config.js | 40 - .../remote-config-compat}/.eslintrc.js | 0 .../remote-config-compat/README.md | 2 +- .../remote-config-compat/karma.conf.js | 0 .../remote-config-compat/package.json | 9 +- .../remote-config-compat/rollup.config.js | 44 +- .../remote-config-compat/src/index.ts | 11 +- .../src/remoteConfig.test.ts | 2 +- .../remote-config-compat/src/remoteConfig.ts | 2 +- .../remote-config-compat}/test/setup.ts | 0 .../remote-config-compat/test/util.ts | 2 +- .../remote-config-compat/tsconfig.json | 0 packages/remote-config-types/index.d.ts | 2 +- packages/remote-config/.eslintrc.js | 17 + .../remote-config}/api-extractor.json | 0 packages/remote-config/index.ts | 142 - packages/remote-config/package.json | 26 +- packages/remote-config/rollup.config.js | 27 +- .../remote-config}/src/api.ts | 2 +- .../remote-config}/src/api2.ts | 0 .../remote-config/src/client/rest_client.ts | 4 +- .../remote-config/src/constants.ts | 2 +- .../remote-config}/src/index.ts | 0 .../remote-config}/src/public_types.ts | 8 +- .../remote-config}/src/register.ts | 8 +- packages/remote-config/src/remote_config.ts | 195 +- .../remote-config/test/remote_config.test.ts | 121 +- packages/remote-config/test_app/index.html | 4 +- packages/remote-config/test_app/index.js | 38 +- packages/rules-unit-testing/index.ts | 21 +- .../rules-unit-testing/mocharc.node.js | 9 +- packages/rules-unit-testing/package.json | 26 +- packages/rules-unit-testing/src/api/index.ts | 743 - .../rules-unit-testing/src/impl/discovery.ts | 158 + packages/rules-unit-testing/src/impl/rules.ts | 89 + .../src/impl/test_environment.ts | 256 + packages/rules-unit-testing/src/impl/url.ts | 55 + packages/rules-unit-testing/src/initialize.ts | 108 + .../src/public_types/index.ts | 278 + packages/rules-unit-testing/src/util.ts | 179 + .../rules-unit-testing/test/database.test.ts | 516 - .../rules-unit-testing/test/dummy.test.ts | 7 +- .../test/impl/discovery.test.ts | 337 + .../test/impl/rules.test.ts | 95 + .../rules-unit-testing}/test/setup.ts | 13 +- .../rules-unit-testing/test/test_utils.ts | 34 +- packages/rules-unit-testing/test/util.test.ts | 204 + .../storage-compat}/.eslintrc.js | 11 + .../storage-compat}/README.md | 4 +- .../storage-compat}/karma.conf.js | 24 +- packages/storage-compat/package.json | 49 + .../rollup.config.js} | 52 +- .../compat => storage-compat/src}/index.ts | 15 +- .../compat => storage-compat/src}/list.ts | 2 +- .../src}/reference.ts | 26 +- .../compat => storage-compat/src}/service.ts | 22 +- .../compat => storage-compat/src}/task.ts | 2 +- .../src}/tasksnapshot.ts | 5 +- .../test/integration/integration.test.ts} | 7 +- .../storage-compat}/test/setup.ts | 4 +- .../test/unit/index.test.ts} | 13 +- .../test/unit/reference.test.ts | 214 + .../storage-compat/test/unit/service.test.ts | 234 + .../test/utils.ts} | 30 +- .../storage-compat}/tsconfig.json | 0 packages/storage-types/index.d.ts | 2 +- packages/storage/.eslintrc.js | 11 - packages/storage/.npmignore | 9 - packages/storage/api-extractor.json | 6 +- packages/storage/compat/package.json | 13 - packages/storage/exp/package.json | 10 - packages/storage/index.ts | 88 - packages/storage/karma.conf.js | 16 +- packages/storage/package.json | 36 +- packages/storage/register-module.ts | 50 - packages/storage/rollup.config.exp.js | 110 - packages/storage/rollup.config.js | 45 +- packages/storage/{exp => src}/api.ts | 33 +- packages/storage/{exp => src}/constants.ts | 2 +- packages/storage/src/implementation/error.ts | 115 +- .../storage/src/implementation/failrequest.ts | 4 +- packages/storage/src/implementation/fs.ts | 4 +- .../storage/src/implementation/observer.ts | 14 +- .../storage/src/implementation/request.ts | 6 +- .../storage/src/implementation/requestinfo.ts | 4 +- .../storage/src/implementation/requests.ts | 16 +- packages/storage/src/implementation/string.ts | 7 +- .../storage/src/implementation/taskenums.ts | 9 +- packages/storage/{exp => src}/index.ts | 4 +- packages/storage/src/metadata.ts | 2 +- packages/storage/{exp => src}/public-types.ts | 18 +- packages/storage/src/reference.ts | 3 +- packages/storage/src/service.ts | 7 +- packages/storage/src/task.ts | 50 +- packages/storage/src/tasksnapshot.ts | 73 - ...ration.exp.test.ts => integration.test.ts} | 10 +- packages/storage/test/unit/connection.ts | 6 +- .../unit/{index.exp.test.ts => index.test.ts} | 4 +- .../test/unit/reference.compat.test.ts | 315 - ...eference.exp.test.ts => reference.test.ts} | 0 .../storage/test/unit/service.compat.test.ts | 370 - .../{service.exp.test.ts => service.test.ts} | 31 +- packages/storage/test/unit/testshared.ts | 10 +- .../size-analysis/package-analysis.ts | 5 +- repo-scripts/size-analysis/package.json | 3 +- .../size-analysis/test/size-analysis.test.ts | 2 +- .../test/test-inputs/assortedImports.ts | 4 +- scripts/check_changeset.ts | 2 +- scripts/ci-test/build_changed.ts | 65 +- scripts/ci-test/tasks.ts | 2 +- scripts/ci-test/testConfig.ts | 3 +- .../emulator-testing/emulators/emulator.ts | 58 +- scripts/exp/docgen.ts | 54 +- scripts/exp/update-api-reports.ts | 60 - scripts/release/utils/yarn.ts | 6 +- scripts/utils.ts | 2 +- yarn.lock | 390 +- 1671 files changed, 13905 insertions(+), 171785 deletions(-) create mode 100644 .changeset/stale-ducks-live.md create mode 100644 .changeset/tame-olives-compete.md rename common/api-review/{analytics-exp.api.md => analytics.api.md} (96%) rename common/api-review/{app-check-exp.api.md => app-check.api.md} (91%) rename common/api-review/{app-exp.api.md => app.api.md} (95%) rename common/api-review/{auth-exp.api.md => auth.api.md} (94%) rename common/api-review/{functions-exp.api.md => functions.api.md} (91%) rename common/api-review/{installations-exp.api.md => installations.api.md} (85%) rename common/api-review/{messaging-exp.sw.api.md => messaging-sw.api.md} (52%) rename common/api-review/{messaging-exp.api.md => messaging.api.md} (88%) rename common/api-review/{performance-exp.api.md => performance.api.md} (88%) rename common/api-review/{remote-config-exp.api.md => remote-config.api.md} (90%) delete mode 100644 integration/firestore/firebase_export_memory.ts delete mode 100644 packages-exp/analytics-compat/rollup.config.js delete mode 100644 packages-exp/analytics-compat/rollup.config.release.js delete mode 100644 packages-exp/analytics-compat/rollup.shared.js delete mode 100644 packages-exp/analytics-exp/.eslintrc.js delete mode 100644 packages-exp/analytics-exp/karma.conf.js delete mode 100644 packages-exp/analytics-exp/karma.integration.conf.js delete mode 100644 packages-exp/analytics-exp/package.json delete mode 100644 packages-exp/analytics-exp/rollup.config.js delete mode 100644 packages-exp/analytics-exp/rollup.config.release.js delete mode 100644 packages-exp/analytics-exp/rollup.shared.js delete mode 100644 packages-exp/analytics-exp/src/constants.ts delete mode 100644 packages-exp/analytics-exp/src/errors.ts delete mode 100644 packages-exp/analytics-exp/src/factory.ts delete mode 100644 packages-exp/analytics-exp/src/functions.test.ts delete mode 100644 packages-exp/analytics-exp/src/functions.ts delete mode 100644 packages-exp/analytics-exp/src/get-config.test.ts delete mode 100644 packages-exp/analytics-exp/src/get-config.ts delete mode 100644 packages-exp/analytics-exp/src/helpers.test.ts delete mode 100644 packages-exp/analytics-exp/src/helpers.ts delete mode 100644 packages-exp/analytics-exp/testing/get-fake-firebase-services.ts delete mode 100644 packages-exp/analytics-exp/testing/gtag-script-util.ts delete mode 100644 packages-exp/analytics-exp/testing/integration-tests/integration.ts delete mode 100644 packages-exp/analytics-exp/testing/setup.ts delete mode 100644 packages-exp/app-check-compat/rollup.config.js delete mode 100644 packages-exp/app-check-compat/rollup.config.release.js delete mode 100644 packages-exp/app-check-compat/rollup.shared.js delete mode 100644 packages-exp/app-check-compat/tsconfig.json delete mode 100644 packages-exp/app-check-exp/README.md delete mode 100644 packages-exp/app-check-exp/package.json delete mode 100644 packages-exp/app-check-exp/rollup.config.js delete mode 100644 packages-exp/app-check-exp/rollup.config.release.js delete mode 100644 packages-exp/app-check-exp/rollup.shared.js delete mode 100644 packages-exp/app-check-exp/src/api.test.ts delete mode 100644 packages-exp/app-check-exp/src/api.ts delete mode 100644 packages-exp/app-check-exp/src/client.test.ts delete mode 100644 packages-exp/app-check-exp/src/client.ts delete mode 100644 packages-exp/app-check-exp/src/constants.ts delete mode 100644 packages-exp/app-check-exp/src/debug.test.ts delete mode 100644 packages-exp/app-check-exp/src/debug.ts delete mode 100644 packages-exp/app-check-exp/src/errors.ts delete mode 100644 packages-exp/app-check-exp/src/factory.ts delete mode 100644 packages-exp/app-check-exp/src/index.ts delete mode 100644 packages-exp/app-check-exp/src/indexeddb.ts delete mode 100644 packages-exp/app-check-exp/src/internal-api.test.ts delete mode 100644 packages-exp/app-check-exp/src/internal-api.ts delete mode 100644 packages-exp/app-check-exp/src/logger.ts delete mode 100644 packages-exp/app-check-exp/src/proactive-refresh.test.ts delete mode 100644 packages-exp/app-check-exp/src/proactive-refresh.ts delete mode 100644 packages-exp/app-check-exp/src/providers.ts delete mode 100644 packages-exp/app-check-exp/src/recaptcha.test.ts delete mode 100644 packages-exp/app-check-exp/src/recaptcha.ts delete mode 100644 packages-exp/app-check-exp/src/state.ts delete mode 100644 packages-exp/app-check-exp/src/storage.test.ts delete mode 100644 packages-exp/app-check-exp/src/storage.ts delete mode 100644 packages-exp/app-check-exp/src/util.ts delete mode 100644 packages-exp/app-check-exp/test/util.ts delete mode 100644 packages-exp/app-check-exp/tsconfig.json delete mode 100644 packages-exp/app-compat/rollup.config.release.js delete mode 100644 packages-exp/app-exp/karma.conf.js delete mode 100644 packages-exp/app-exp/package.json delete mode 100644 packages-exp/app-exp/rollup.config.release.js delete mode 100644 packages-exp/app-exp/rollup.shared.js delete mode 100644 packages-exp/app-exp/src/constants.ts delete mode 100644 packages-exp/app-exp/src/errors.ts delete mode 100644 packages-exp/app-exp/src/firebaseApp.ts delete mode 100644 packages-exp/app-exp/src/logger.ts delete mode 100644 packages-exp/app-exp/src/platformLoggerService.ts delete mode 100644 packages-exp/app-exp/src/registerCoreComponents.ts delete mode 100644 packages-exp/auth-compat-exp/demo/public/index.html delete mode 100644 packages-exp/auth-compat-exp/demo/public/service-worker.js delete mode 100644 packages-exp/auth-compat-exp/demo/public/web-worker.js delete mode 100644 packages-exp/auth-compat-exp/rollup.config.js delete mode 100644 packages-exp/auth-compat-exp/rollup.config.release.js delete mode 100644 packages-exp/auth-compat-exp/rollup.config.shared.js delete mode 100644 packages-exp/auth-exp/README.md delete mode 100644 packages-exp/auth-exp/demo/.gitignore delete mode 100644 packages-exp/auth-exp/demo/README.md delete mode 100644 packages-exp/auth-exp/demo/database.rules.json delete mode 100644 packages-exp/auth-exp/demo/firebase.json delete mode 100644 packages-exp/auth-exp/demo/functions/index.js delete mode 100644 packages-exp/auth-exp/demo/functions/package.json delete mode 100644 packages-exp/auth-exp/demo/functions/yarn.lock delete mode 100644 packages-exp/auth-exp/demo/public/common.js delete mode 100644 packages-exp/auth-exp/demo/public/index.html delete mode 100644 packages-exp/auth-exp/demo/public/manifest.json delete mode 100644 packages-exp/auth-exp/demo/public/style.css delete mode 100644 packages-exp/auth-exp/package.json delete mode 100644 packages-exp/auth-exp/rollup.config.release.js delete mode 100644 packages-exp/auth-exp/rollup.config.shared.js delete mode 100644 packages-exp/firebase-exp/.gitignore delete mode 100644 packages-exp/firebase-exp/README.md delete mode 100644 packages-exp/firebase-exp/analytics/index.ts delete mode 100644 packages-exp/firebase-exp/analytics/package.json delete mode 100644 packages-exp/firebase-exp/app-check/package.json delete mode 100644 packages-exp/firebase-exp/app/index.ts delete mode 100644 packages-exp/firebase-exp/app/package.json delete mode 100644 packages-exp/firebase-exp/auth/cordova/package.json delete mode 100644 packages-exp/firebase-exp/auth/index.ts delete mode 100644 packages-exp/firebase-exp/auth/package.json delete mode 100644 packages-exp/firebase-exp/auth/react-native/package.json delete mode 100644 packages-exp/firebase-exp/compat/analytics/package.json delete mode 100644 packages-exp/firebase-exp/compat/app-check/package.json delete mode 100644 packages-exp/firebase-exp/compat/app/package.json delete mode 100644 packages-exp/firebase-exp/compat/auth/package.json delete mode 100644 packages-exp/firebase-exp/compat/database/package.json delete mode 100644 packages-exp/firebase-exp/compat/firestore/package.json delete mode 100644 packages-exp/firebase-exp/compat/functions/package.json delete mode 100644 packages-exp/firebase-exp/compat/index.d.ts delete mode 100644 packages-exp/firebase-exp/compat/messaging/package.json delete mode 100644 packages-exp/firebase-exp/compat/performance/package.json delete mode 100644 packages-exp/firebase-exp/compat/remote-config/package.json delete mode 100644 packages-exp/firebase-exp/compat/rollup.config.release.js delete mode 100644 packages-exp/firebase-exp/compat/storage/package.json delete mode 100644 packages-exp/firebase-exp/database/package.json delete mode 100644 packages-exp/firebase-exp/firestore/lite/package.json delete mode 100644 packages-exp/firebase-exp/firestore/package.json delete mode 100644 packages-exp/firebase-exp/functions/index.ts delete mode 100644 packages-exp/firebase-exp/functions/package.json delete mode 100644 packages-exp/firebase-exp/messaging/index.ts delete mode 100644 packages-exp/firebase-exp/messaging/package.json delete mode 100644 packages-exp/firebase-exp/messaging/sw/index.ts delete mode 100644 packages-exp/firebase-exp/messaging/sw/package.json delete mode 100644 packages-exp/firebase-exp/package.json delete mode 100644 packages-exp/firebase-exp/performance/index.ts delete mode 100644 packages-exp/firebase-exp/performance/package.json delete mode 100644 packages-exp/firebase-exp/remote-config/index.ts delete mode 100644 packages-exp/firebase-exp/remote-config/package.json delete mode 100644 packages-exp/firebase-exp/rollup.config.js delete mode 100644 packages-exp/firebase-exp/rollup.config.release.js delete mode 100644 packages-exp/firebase-exp/storage/index.ts delete mode 100644 packages-exp/firebase-exp/storage/package.json delete mode 100644 packages-exp/functions-compat/rollup.config.base.js delete mode 100644 packages-exp/functions-compat/rollup.config.js delete mode 100644 packages-exp/functions-compat/rollup.config.release.js delete mode 100644 packages-exp/functions-exp/package.json delete mode 100644 packages-exp/functions-exp/rollup.config.js delete mode 100644 packages-exp/functions-exp/rollup.config.release.js delete mode 100644 packages-exp/functions-exp/rollup.shared.js delete mode 100644 packages-exp/functions-exp/src/config.ts delete mode 100644 packages-exp/functions-exp/src/context.ts delete mode 100644 packages-exp/functions-exp/src/serializer.ts delete mode 100644 packages-exp/functions-exp/test/utils.ts delete mode 100644 packages-exp/functions-exp/tsconfig.json delete mode 100644 packages-exp/installations-compat/rollup.config.js delete mode 100644 packages-exp/installations-compat/rollup.config.release.js delete mode 100644 packages-exp/installations-exp/karma.conf.js delete mode 100644 packages-exp/installations-exp/package.json delete mode 100644 packages-exp/installations-exp/rollup.config.js delete mode 100644 packages-exp/installations-exp/rollup.config.release.js delete mode 100644 packages-exp/installations-exp/rollup.shared.js delete mode 100644 packages-exp/installations-exp/src/helpers/buffer-to-base64-url-safe.test.ts delete mode 100644 packages-exp/installations-exp/src/helpers/buffer-to-base64-url-safe.ts delete mode 100644 packages-exp/installations-exp/src/helpers/extract-app-config.test.ts delete mode 100644 packages-exp/installations-exp/src/helpers/extract-app-config.ts delete mode 100644 packages-exp/installations-exp/src/helpers/fid-changed.test.ts delete mode 100644 packages-exp/installations-exp/src/helpers/fid-changed.ts delete mode 100644 packages-exp/installations-exp/src/helpers/generate-fid.test.ts delete mode 100644 packages-exp/installations-exp/src/helpers/generate-fid.ts delete mode 100644 packages-exp/installations-exp/src/helpers/get-installation-entry.test.ts delete mode 100644 packages-exp/installations-exp/src/helpers/get-installation-entry.ts delete mode 100644 packages-exp/installations-exp/src/helpers/idb-manager.test.ts delete mode 100644 packages-exp/installations-exp/src/helpers/idb-manager.ts delete mode 100644 packages-exp/installations-exp/src/helpers/refresh-auth-token.test.ts delete mode 100644 packages-exp/installations-exp/src/helpers/refresh-auth-token.ts delete mode 100644 packages-exp/installations-exp/src/index.ts delete mode 100644 packages-exp/installations-exp/src/interfaces/api-response.ts delete mode 100644 packages-exp/installations-exp/src/interfaces/installation-entry.ts delete mode 100644 packages-exp/installations-exp/src/testing/compare-headers.test.ts delete mode 100644 packages-exp/installations-exp/src/testing/compare-headers.ts delete mode 100644 packages-exp/installations-exp/src/testing/fake-generators.ts delete mode 100644 packages-exp/installations-exp/src/testing/setup.ts delete mode 100644 packages-exp/installations-exp/src/util/constants.ts delete mode 100644 packages-exp/installations-exp/src/util/errors.ts delete mode 100644 packages-exp/installations-exp/src/util/get-key.ts delete mode 100644 packages-exp/installations-exp/src/util/sleep.test.ts delete mode 100644 packages-exp/installations-exp/src/util/sleep.ts delete mode 100644 packages-exp/installations-exp/test-app/.gitignore delete mode 100644 packages-exp/installations-exp/test-app/index.html delete mode 100644 packages-exp/installations-exp/test-app/index.js delete mode 100644 packages-exp/installations-exp/test-app/rollup.config.js delete mode 100644 packages-exp/installations-exp/tsconfig.json delete mode 100644 packages-exp/messaging-exp/.eslintrc.js delete mode 100644 packages-exp/messaging-exp/README.md delete mode 100644 packages-exp/messaging-exp/karma.conf.js delete mode 100644 packages-exp/messaging-exp/package.json delete mode 100644 packages-exp/messaging-exp/rollup.config.release.js delete mode 100644 packages-exp/messaging-exp/src/helpers/array-base64-translator.test.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/array-base64-translator.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/externalizePayload.test.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/externalizePayload.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/extract-app-config.test.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/extract-app-config.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/is-console-message.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/migrate-old-database.test.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/migrate-old-database.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/sleep.test.ts delete mode 100644 packages-exp/messaging-exp/src/helpers/sleep.ts delete mode 100644 packages-exp/messaging-exp/src/index.ts delete mode 100644 packages-exp/messaging-exp/src/interfaces/app-config.ts delete mode 100644 packages-exp/messaging-exp/src/interfaces/internal-dependencies.ts delete mode 100644 packages-exp/messaging-exp/src/interfaces/internal-message-payload.ts delete mode 100644 packages-exp/messaging-exp/src/interfaces/token-details.ts delete mode 100644 packages-exp/messaging-exp/src/testing/compare-headers.test.ts delete mode 100644 packages-exp/messaging-exp/src/testing/compare-headers.ts delete mode 100644 packages-exp/messaging-exp/src/testing/fakes/firebase-dependencies.ts delete mode 100644 packages-exp/messaging-exp/src/testing/fakes/service-worker.ts delete mode 100644 packages-exp/messaging-exp/src/testing/fakes/token-details.ts delete mode 100644 packages-exp/messaging-exp/src/testing/setup.ts delete mode 100644 packages-exp/messaging-exp/src/testing/sinon-types.ts delete mode 100644 packages-exp/messaging-exp/src/util/constants.ts delete mode 100644 packages-exp/messaging-exp/src/util/errors.ts delete mode 100644 packages-exp/messaging-exp/src/util/sw-types.ts delete mode 100644 packages-exp/messaging-exp/tsconfig.json delete mode 100644 packages-exp/performance-compat/.eslintrc.js delete mode 100644 packages-exp/performance-compat/rollup.config.js delete mode 100644 packages-exp/performance-compat/rollup.config.release.js delete mode 100644 packages-exp/performance-exp/README.md delete mode 100644 packages-exp/performance-exp/karma.conf.js delete mode 100644 packages-exp/performance-exp/package.json delete mode 100644 packages-exp/performance-exp/rollup.config.js delete mode 100644 packages-exp/performance-exp/rollup.config.release.js delete mode 100644 packages-exp/performance-exp/rollup.shared.js delete mode 100644 packages-exp/performance-exp/src/constants.ts delete mode 100644 packages-exp/performance-exp/src/controllers/perf.test.ts delete mode 100644 packages-exp/performance-exp/src/controllers/perf.ts delete mode 100644 packages-exp/performance-exp/src/resources/network_request.test.ts delete mode 100644 packages-exp/performance-exp/src/resources/network_request.ts delete mode 100644 packages-exp/performance-exp/src/resources/trace.test.ts delete mode 100644 packages-exp/performance-exp/src/resources/trace.ts delete mode 100644 packages-exp/performance-exp/src/services/api_service.test.ts delete mode 100644 packages-exp/performance-exp/src/services/api_service.ts delete mode 100644 packages-exp/performance-exp/src/services/iid_service.test.ts delete mode 100644 packages-exp/performance-exp/src/services/iid_service.ts delete mode 100644 packages-exp/performance-exp/src/services/initialization_service.test.ts delete mode 100644 packages-exp/performance-exp/src/services/initialization_service.ts delete mode 100644 packages-exp/performance-exp/src/services/oob_resources_service.test.ts delete mode 100644 packages-exp/performance-exp/src/services/oob_resources_service.ts delete mode 100644 packages-exp/performance-exp/src/services/perf_logger.test.ts delete mode 100644 packages-exp/performance-exp/src/services/perf_logger.ts delete mode 100644 packages-exp/performance-exp/src/services/remote_config_service.test.ts delete mode 100644 packages-exp/performance-exp/src/services/remote_config_service.ts delete mode 100644 packages-exp/performance-exp/src/services/settings_service.ts delete mode 100644 packages-exp/performance-exp/src/services/transport_service.test.ts delete mode 100644 packages-exp/performance-exp/src/services/transport_service.ts delete mode 100644 packages-exp/performance-exp/src/utils/attribute_utils.test.ts delete mode 100644 packages-exp/performance-exp/src/utils/attributes_utils.ts delete mode 100644 packages-exp/performance-exp/src/utils/console_logger.ts delete mode 100644 packages-exp/performance-exp/src/utils/errors.ts delete mode 100644 packages-exp/performance-exp/src/utils/metric_utils.test.ts delete mode 100644 packages-exp/performance-exp/src/utils/metric_utils.ts delete mode 100644 packages-exp/performance-exp/src/utils/string_merger.test.ts delete mode 100644 packages-exp/performance-exp/src/utils/string_merger.ts delete mode 100644 packages-exp/performance-exp/tsconfig.json delete mode 100644 packages-exp/remote-config-compat/.eslintrc.js delete mode 100644 packages-exp/remote-config-compat/rollup.config.js delete mode 100644 packages-exp/remote-config-compat/rollup.config.release.js delete mode 100644 packages-exp/remote-config-compat/rollup.shared.js delete mode 100644 packages-exp/remote-config-compat/test/setup.ts delete mode 100644 packages-exp/remote-config-exp/.eslintrc.js delete mode 100644 packages-exp/remote-config-exp/.npmignore delete mode 100644 packages-exp/remote-config-exp/README.md delete mode 100644 packages-exp/remote-config-exp/package.json delete mode 100644 packages-exp/remote-config-exp/rollup.config.js delete mode 100644 packages-exp/remote-config-exp/rollup.config.release.js delete mode 100644 packages-exp/remote-config-exp/rollup.shared.js delete mode 100644 packages-exp/remote-config-exp/src/client/caching_client.ts delete mode 100644 packages-exp/remote-config-exp/src/client/remote_config_fetch_client.ts delete mode 100644 packages-exp/remote-config-exp/src/client/rest_client.ts delete mode 100644 packages-exp/remote-config-exp/src/client/retrying_client.ts delete mode 100644 packages-exp/remote-config-exp/src/constants.ts delete mode 100644 packages-exp/remote-config-exp/src/errors.ts delete mode 100644 packages-exp/remote-config-exp/src/language.ts delete mode 100644 packages-exp/remote-config-exp/src/remote_config.ts delete mode 100644 packages-exp/remote-config-exp/src/storage/storage.ts delete mode 100644 packages-exp/remote-config-exp/src/storage/storage_cache.ts delete mode 100644 packages-exp/remote-config-exp/src/value.ts delete mode 100644 packages-exp/remote-config-exp/test/client/caching_client.test.ts delete mode 100644 packages-exp/remote-config-exp/test/client/rest_client.test.ts delete mode 100644 packages-exp/remote-config-exp/test/client/retrying_client.test.ts delete mode 100644 packages-exp/remote-config-exp/test/errors.test.ts delete mode 100644 packages-exp/remote-config-exp/test/language.test.ts delete mode 100644 packages-exp/remote-config-exp/test/remote_config.test.ts delete mode 100644 packages-exp/remote-config-exp/test/setup.ts delete mode 100644 packages-exp/remote-config-exp/test/storage/storage.test.ts delete mode 100644 packages-exp/remote-config-exp/test/storage/storage_cache.test.ts delete mode 100644 packages-exp/remote-config-exp/test/value.test.ts delete mode 100644 packages-exp/remote-config-exp/test_app/index.html delete mode 100644 packages-exp/remote-config-exp/test_app/index.js delete mode 100644 packages-exp/remote-config-exp/tsconfig.json rename {packages-exp => packages}/analytics-compat/.eslintrc.js (100%) rename {packages-exp/functions-exp => packages/analytics-compat}/README.md (57%) rename {packages-exp => packages}/analytics-compat/karma.conf.js (100%) rename {packages-exp => packages}/analytics-compat/package.json (81%) rename {packages-exp/messaging-exp => packages/analytics-compat}/rollup.config.js (78%) rename {packages-exp => packages}/analytics-compat/src/constants.ts (100%) rename {packages-exp => packages}/analytics-compat/src/index.ts (92%) rename {packages-exp => packages}/analytics-compat/src/service.test.ts (98%) rename {packages-exp => packages}/analytics-compat/src/service.ts (98%) rename {packages-exp => packages}/analytics-compat/tsconfig.json (100%) rename {packages-exp/analytics-exp => packages/analytics}/api-extractor.json (100%) delete mode 100644 packages/analytics/index.test.ts delete mode 100644 packages/analytics/index.ts rename {packages-exp/analytics-exp => packages/analytics}/src/api.test.ts (98%) rename {packages-exp/analytics-exp => packages/analytics}/src/api.ts (99%) rename {packages-exp/analytics-exp => packages/analytics}/src/index.test.ts (99%) rename {packages-exp/analytics-exp => packages/analytics}/src/index.ts (93%) rename {packages-exp/analytics-exp => packages/analytics}/src/initialize-analytics.test.ts (98%) rename {packages-exp/analytics-exp => packages/analytics}/src/initialize-analytics.ts (98%) delete mode 100644 packages/analytics/src/initialize-ids.test.ts delete mode 100644 packages/analytics/src/initialize-ids.ts rename {packages-exp/analytics-exp => packages/analytics}/src/public-types.ts (99%) rename {packages-exp/analytics-exp => packages/analytics}/src/types.ts (100%) rename {packages-exp => packages}/app-check-compat/.eslintrc.js (100%) rename {packages-exp => packages}/app-check-compat/README.md (100%) rename {packages-exp => packages}/app-check-compat/karma.conf.js (100%) rename {packages-exp => packages}/app-check-compat/package.json (84%) rename packages-exp/messaging-compat/rollup.config.release.js => packages/app-check-compat/rollup.config.js (72%) rename packages-exp/firebase-exp/auth/react-native/index.ts => packages/app-check-compat/rollup.shared.js (92%) rename {packages-exp => packages}/app-check-compat/src/errors.ts (100%) rename {packages-exp => packages}/app-check-compat/src/index.ts (93%) rename {packages-exp => packages}/app-check-compat/src/service.test.ts (98%) rename {packages-exp => packages}/app-check-compat/src/service.ts (98%) rename {packages-exp/analytics-exp => packages/app-check-compat}/tsconfig.json (100%) rename {packages-exp/app-check-exp => packages/app-check}/api-extractor.json (100%) rename {packages-exp/app-check-exp => packages/app-check}/src/public-types.ts (95%) rename {packages-exp/app-check-exp => packages/app-check}/src/types.ts (98%) rename {packages-exp => packages}/app-compat/.eslintrc.js (100%) rename {packages-exp => packages}/app-compat/README.md (67%) rename {packages-exp => packages}/app-compat/karma.conf.js (100%) rename {packages-exp => packages}/app-compat/package.json (88%) rename {packages-exp => packages}/app-compat/rollup.config.js (100%) rename {packages-exp => packages}/app-compat/src/errors.ts (100%) rename {packages-exp => packages}/app-compat/src/firebaseApp.ts (99%) rename {packages-exp => packages}/app-compat/src/firebaseNamespace.ts (100%) rename {packages-exp => packages}/app-compat/src/firebaseNamespaceCore.ts (99%) rename {packages-exp => packages}/app-compat/src/index.lite.ts (100%) rename {packages-exp => packages}/app-compat/src/index.ts (100%) rename {packages-exp => packages}/app-compat/src/lite/firebaseAppLite.ts (98%) rename {packages-exp => packages}/app-compat/src/lite/firebaseNamespaceLite.ts (100%) rename {packages-exp => packages}/app-compat/src/logger.ts (100%) rename {packages-exp => packages}/app-compat/src/public-types.ts (100%) rename {packages-exp => packages}/app-compat/src/registerCoreComponents.ts (93%) rename {packages-exp => packages}/app-compat/src/types.ts (100%) rename {packages-exp => packages}/app-compat/test/firebaseAppCompat.test.ts (99%) rename {packages-exp => packages}/app-compat/test/setup.ts (100%) rename {packages-exp => packages}/app-compat/test/util.ts (100%) rename {packages-exp => packages}/app-compat/tsconfig.json (100%) rename {packages-exp/auth-exp => packages/app}/api-extractor.json (81%) delete mode 100644 packages/app/index.lite.ts delete mode 100644 packages/app/index.node.ts delete mode 100644 packages/app/index.rn.ts delete mode 100644 packages/app/index.ts rename {packages-exp/app-exp => packages/app}/src/api.test.ts (99%) rename {packages-exp/app-exp => packages/app}/src/api.ts (98%) rename {packages-exp/app-exp => packages/app}/src/firebaseApp.test.ts (100%) delete mode 100644 packages/app/src/firebaseNamespace.ts delete mode 100644 packages/app/src/firebaseNamespaceCore.ts rename {packages-exp/app-exp => packages/app}/src/index.ts (100%) rename {packages-exp/app-exp => packages/app}/src/internal.test.ts (100%) rename {packages-exp/app-exp => packages/app}/src/internal.ts (100%) delete mode 100644 packages/app/src/lite/firebaseAppLite.ts delete mode 100644 packages/app/src/lite/firebaseNamespaceLite.ts rename {packages-exp/app-exp => packages/app}/src/platformLoggerService.test.ts (100%) rename {packages-exp/app-exp => packages/app}/src/public-types.ts (98%) rename {packages-exp/app-exp => packages/app}/src/types.ts (100%) delete mode 100644 packages/app/test/clientLogger.test.ts delete mode 100644 packages/app/test/firebaseApp.test.ts delete mode 100644 packages/app/test/platformLogger.test.ts rename {packages-exp/app-exp => packages/app}/test/util.ts (95%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/.eslintrc.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/README.md (89%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/.eslintignore (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/.eslintrc.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/.gitignore (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/README.md (87%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/database.rules.json (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/firebase.json (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/functions/index.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/functions/package.json (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/functions/yarn.lock (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/package.json (85%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/public/common.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/public/manifest.json (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/public/sample-config.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/public/script.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/public/style.css (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/rollup.config.js (92%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/tsconfig.json (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/demo/yarn.lock (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/index.node.ts (94%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/index.ts (93%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/karma.conf.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/package.json (81%) create mode 100644 packages/auth-compat/rollup.config.js rename {packages-exp/auth-compat-exp => packages/auth-compat}/scripts/run_node_tests.ts (94%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/auth.test.ts (93%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/auth.ts (99%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/persistence.ts (98%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/phone_auth_provider.ts (97%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/platform.ts (98%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/popup_redirect.test.ts (99%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/popup_redirect.ts (98%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/recaptcha_verifier.ts (97%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/user.ts (99%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/user_credential.ts (99%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/src/wrap.ts (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/helpers/helpers.ts (88%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/flows/anonymous.test.ts (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/flows/custom.test.ts (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/flows/email.test.ts (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/flows/idp.test.ts (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/flows/oob.test.ts (99%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/flows/phone.test.ts (98%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/anonymous.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/core.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/email.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/index.html (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/index.js (89%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/lazy_load.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/logged_in.html (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/persistence.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/popup.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/redirect.js (100%) rename {packages-exp/auth-exp => packages/auth-compat}/test/integration/webdriver/static/rollup.config.js (94%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/test/integration/webdriver/static/ui.js (100%) rename {packages-exp/auth-compat-exp => packages/auth-compat}/tsconfig.json (100%) rename {packages-exp/auth-exp => packages/auth}/.eslintrc.js (100%) delete mode 100644 packages/auth/.gitignore delete mode 100644 packages/auth/CONTRIBUTING.md delete mode 100644 packages/auth/LICENSE delete mode 100644 packages/auth/STYLEGUIDE.md rename {packages-exp/app-exp => packages/auth}/api-extractor.json (62%) delete mode 100644 packages/auth/buildtools/all_tests.html delete mode 100644 packages/auth/buildtools/common.py delete mode 100644 packages/auth/buildtools/gen_all_tests_js.py delete mode 100644 packages/auth/buildtools/gen_test_html.py delete mode 100755 packages/auth/buildtools/generate_test_files.sh delete mode 100755 packages/auth/buildtools/run_demo.sh delete mode 100755 packages/auth/buildtools/run_tests.sh delete mode 100755 packages/auth/buildtools/sauce_connect.sh delete mode 100644 packages/auth/buildtools/test_template.html rename {packages-exp/auth-exp => packages/auth}/cordova/demo/.gitignore (100%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/README.md (100%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/package.json (87%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/rollup.config.js (100%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/sample-config.xml (100%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/src/index.js (99%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/src/logging.js (100%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/src/sample-config.js (100%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/www/index.html (100%) rename {packages-exp/auth-exp => packages/auth}/cordova/demo/www/style.css (100%) rename {packages-exp/auth-exp => packages/auth}/cordova/package.json (68%) rename {packages-exp/auth-exp => packages/auth}/demo/.eslintignore (100%) rename {packages-exp/auth-exp => packages/auth}/demo/.eslintrc.js (100%) rename {packages-exp/auth-exp => packages/auth}/demo/package.json (84%) delete mode 100644 packages/auth/demo/public/sample-config.js delete mode 100644 packages/auth/demo/public/script.js delete mode 100644 packages/auth/demo/public/service-worker.js delete mode 100644 packages/auth/demo/public/web-worker.js rename {packages-exp/auth-exp => packages/auth}/demo/rollup.config.js (100%) rename {packages-exp/auth-exp => packages/auth}/demo/src/config.d.ts (100%) rename {packages-exp/auth-exp => packages/auth}/demo/src/index.js (99%) rename {packages-exp/auth-exp => packages/auth}/demo/src/logging.js (100%) rename {packages-exp/auth-exp => packages/auth}/demo/src/sample-config.js (100%) rename {packages-exp/auth-exp => packages/auth}/demo/src/worker/service-worker.ts (98%) rename {packages-exp/auth-exp => packages/auth}/demo/src/worker/tsconfig.json (100%) rename {packages-exp/auth-exp => packages/auth}/demo/src/worker/web-worker.ts (98%) rename {packages-exp/auth-exp => packages/auth}/demo/tsconfig.json (100%) delete mode 100644 packages/auth/externs/externs.js delete mode 100644 packages/auth/externs/gapi.iframes.js delete mode 100644 packages/auth/externs/grecaptcha.js delete mode 100644 packages/auth/gulpfile.js rename {packages-exp/auth-exp => packages/auth}/index.cordova.ts (97%) delete mode 100644 packages/auth/index.d.ts rename {packages-exp/auth-exp => packages/auth}/index.doc.ts (93%) rename {packages-exp/auth-exp => packages/auth}/index.node.ts (100%) rename {packages-exp/auth-exp => packages/auth}/index.rn.ts (97%) rename {packages-exp/auth-exp => packages/auth}/index.shared.ts (100%) rename {packages-exp/auth-exp => packages/auth}/index.ts (100%) rename {packages-exp/auth-exp => packages/auth}/index.webworker.ts (99%) rename {packages-exp/auth-exp => packages/auth}/internal/index.ts (98%) rename {packages-exp/auth-exp => packages/auth}/internal/package.json (88%) rename {packages-exp/auth-exp => packages/auth}/karma.conf.js (100%) delete mode 100644 packages/auth/protractor.conf.js delete mode 100644 packages/auth/protractor_spec.js rename {packages-exp/auth-exp => packages/auth}/react-native/package.json (67%) create mode 100644 packages/auth/rollup.config.js delete mode 100644 packages/auth/sauce_browsers.json rename {packages-exp/auth-exp => packages/auth}/scripts/run_node_tests.ts (100%) delete mode 100644 packages/auth/src/actioncodeinfo.js delete mode 100644 packages/auth/src/actioncodesettings.js delete mode 100644 packages/auth/src/actioncodeurl.js delete mode 100644 packages/auth/src/additionaluserinfo.js rename {packages-exp/auth-exp => packages/auth}/src/api/account_management/account.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/account_management/account.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/account_management/email_and_password.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/account_management/email_and_password.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/account_management/mfa.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/account_management/mfa.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/account_management/profile.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/account_management/profile.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/create_auth_uri.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/create_auth_uri.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/custom_token.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/custom_token.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/email_and_password.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/email_and_password.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/email_link.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/email_link.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/idp.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/idp.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/mfa.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/mfa.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/recaptcha.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/recaptcha.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/sign_up.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/sign_up.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/sms.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/sms.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/token.test.ts (98%) rename {packages-exp/auth-exp => packages/auth}/src/api/authentication/token.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/errors.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/index.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/src/api/index.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/project_config/get_project_config.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/api/project_config/get_project_config.ts (100%) delete mode 100644 packages/auth/src/args.js delete mode 100644 packages/auth/src/auth.js delete mode 100644 packages/auth/src/authcredential.js delete mode 100644 packages/auth/src/authevent.js delete mode 100644 packages/auth/src/autheventmanager.js delete mode 100644 packages/auth/src/authsettings.js delete mode 100644 packages/auth/src/authstorage.js delete mode 100644 packages/auth/src/authuser.js delete mode 100644 packages/auth/src/cacherequest.js delete mode 100644 packages/auth/src/confirmationresult.js delete mode 100644 packages/auth/src/cordovahandler.js rename {packages-exp/auth-exp => packages/auth}/src/core/action_code_url.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/action_code_url.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/auth_event_manager.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/auth_event_manager.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/auth_impl.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/auth_impl.ts (99%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/emulator.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/emulator.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/firebase_internal.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/firebase_internal.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/initialize.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/initialize.ts (96%) rename {packages-exp/auth-exp => packages/auth}/src/core/auth/register.ts (97%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/auth_credential.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/email.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/email.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/index.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/oauth.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/oauth.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/phone.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/phone.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/saml.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/credentials/saml.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/errors.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/errors.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/index.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/persistence/in_memory.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/persistence/in_memory.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/persistence/index.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/persistence/persistence_user_manager.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/persistence/persistence_user_manager.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/email.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/email.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/facebook.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/facebook.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/federated.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/federated.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/github.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/github.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/google.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/google.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/oauth.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/oauth.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/saml.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/saml.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/twitter.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/providers/twitter.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/abstract_popup_redirect_operation.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/abstract_popup_redirect_operation.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/action_code_settings.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/action_code_settings.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/anonymous.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/anonymous.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/credential.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/credential.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/custom_token.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/custom_token.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/email.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/email.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/email_and_password.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/email_and_password.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/email_link.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/email_link.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/idp.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/idp.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/redirect.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/strategies/redirect.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/account_info.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/account_info.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/additional_user_info.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/additional_user_info.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/id_token_result.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/id_token_result.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/invalidation.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/invalidation.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/link_unlink.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/link_unlink.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/proactive_refresh.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/proactive_refresh.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/reauthenticate.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/reauthenticate.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/reload.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/reload.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/token_manager.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/token_manager.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/user_credential_impl.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/user_credential_impl.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/user_impl.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/user_impl.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/user/user_metadata.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/assert.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/assert.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/browser.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/browser.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/delay.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/delay.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/emulator.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/emulator.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/event_id.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/event_id.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/fetch_provider.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/handler.ts (98%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/instantiator.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/instantiator.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/location.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/log.ts (92%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/navigator.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/providers.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/resolver.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/time.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/validate_origin.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/validate_origin.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/version.test.ts (97%) rename {packages-exp/auth-exp => packages/auth}/src/core/util/version.ts (97%) delete mode 100644 packages/auth/src/debug.js delete mode 100644 packages/auth/src/defines.js delete mode 100644 packages/auth/src/deprecation.js delete mode 100644 packages/auth/src/dynamiclink.js delete mode 100644 packages/auth/src/error_auth.js delete mode 100644 packages/auth/src/error_invalidorigin.js delete mode 100644 packages/auth/src/error_withcredential.js delete mode 100644 packages/auth/src/exports_auth.js delete mode 100644 packages/auth/src/exports_lib.js delete mode 100644 packages/auth/src/exports_unreleased.js delete mode 100644 packages/auth/src/externs.js delete mode 100644 packages/auth/src/idp.js delete mode 100644 packages/auth/src/idtoken.js delete mode 100644 packages/auth/src/idtokenresult.js delete mode 100644 packages/auth/src/iframeclient/ifchandler.js delete mode 100644 packages/auth/src/iframeclient/iframewrapper.js rename {packages-exp/auth-exp => packages/auth}/src/index.ts (100%) delete mode 100644 packages/auth/src/messagechannel/defines.js delete mode 100644 packages/auth/src/messagechannel/postmessager.js delete mode 100644 packages/auth/src/messagechannel/receiver.js delete mode 100644 packages/auth/src/messagechannel/sender.js rename {packages-exp/auth-exp => packages/auth}/src/mfa/index.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_assertion.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_error.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_info.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_info.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_resolver.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_resolver.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_session.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_session.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_user.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/mfa/mfa_user.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/model/application_verifier.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/model/auth.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/model/enum_maps.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/model/enums.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/model/id_token.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/model/popup_redirect.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/model/public_types.ts (99%) rename {packages-exp/auth-exp => packages/auth}/src/model/user.ts (100%) delete mode 100644 packages/auth/src/multifactorassertion.js delete mode 100644 packages/auth/src/multifactorauthcredential.js delete mode 100644 packages/auth/src/multifactorerror.js delete mode 100644 packages/auth/src/multifactorgenerator.js delete mode 100644 packages/auth/src/multifactorinfo.js delete mode 100644 packages/auth/src/multifactorresolver.js delete mode 100644 packages/auth/src/multifactorsession.js delete mode 100644 packages/auth/src/multifactoruser.js delete mode 100644 packages/auth/src/oauthhelperstate.js delete mode 100644 packages/auth/src/oauthsigninhandler.js delete mode 100644 packages/auth/src/object.js rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/auth.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/auth_window.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/iframe/gapi.iframes.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/iframe/gapi.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/iframe/gapi.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/iframe/iframe.test.ts (98%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/iframe/iframe.ts (98%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/index.ts (96%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/load_js.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/load_js.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/messagechannel/index.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/messagechannel/promise.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/messagechannel/promise.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/messagechannel/receiver.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/messagechannel/receiver.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/messagechannel/sender.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/messagechannel/sender.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/mfa/assertions/phone.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/mfa/assertions/phone.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/persistence/browser.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/persistence/indexed_db.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/persistence/indexed_db.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/persistence/local_storage.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/persistence/local_storage.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/persistence/session_storage.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/persistence/session_storage.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/popup_redirect.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/popup_redirect.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/providers/phone.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/providers/phone.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/recaptcha/recaptcha.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/recaptcha/recaptcha_loader.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/recaptcha/recaptcha_loader.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/recaptcha/recaptcha_mock.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/recaptcha/recaptcha_mock.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/recaptcha/recaptcha_verifier.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/recaptcha/recaptcha_verifier.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/strategies/phone.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/strategies/phone.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/strategies/popup.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/strategies/popup.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/strategies/redirect.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/strategies/redirect.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/util/popup.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/util/popup.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_browser/util/worker.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_cordova/plugins.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_cordova/popup_redirect/events.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_cordova/popup_redirect/events.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_cordova/popup_redirect/popup_redirect.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_cordova/popup_redirect/popup_redirect.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_cordova/popup_redirect/utils.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_cordova/popup_redirect/utils.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_cordova/strategies/redirect.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_node/index.ts (98%) rename {packages-exp/auth-exp => packages/auth}/src/platform_react_native/persistence/react_native.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_react_native/persistence/react_native.ts (100%) rename {packages-exp/auth-exp => packages/auth}/src/platform_react_native/react-native.d.ts (100%) delete mode 100644 packages/auth/src/proactiverefresh.js delete mode 100644 packages/auth/src/recaptchaverifier/grecaptcha.js delete mode 100644 packages/auth/src/recaptchaverifier/grecaptchamock.js delete mode 100644 packages/auth/src/recaptchaverifier/loader.js delete mode 100644 packages/auth/src/recaptchaverifier/mockloader.js delete mode 100644 packages/auth/src/recaptchaverifier/realloader.js delete mode 100644 packages/auth/src/recaptchaverifier/recaptchaverifier.js delete mode 100644 packages/auth/src/rpchandler.js delete mode 100644 packages/auth/src/storage/asyncstorage.js delete mode 100644 packages/auth/src/storage/factory.js delete mode 100644 packages/auth/src/storage/hybridindexeddb.js delete mode 100644 packages/auth/src/storage/indexeddb.js delete mode 100644 packages/auth/src/storage/inmemorystorage.js delete mode 100644 packages/auth/src/storage/localstorage.js delete mode 100644 packages/auth/src/storage/mockstorage.js delete mode 100644 packages/auth/src/storage/nullstorage.js delete mode 100644 packages/auth/src/storage/sessionstorage.js delete mode 100644 packages/auth/src/storage/storage.js delete mode 100644 packages/auth/src/storageautheventmanager.js delete mode 100644 packages/auth/src/storageoauthhandlermanager.js delete mode 100644 packages/auth/src/storagependingredirectmanager.js delete mode 100644 packages/auth/src/storageredirectusermanager.js delete mode 100644 packages/auth/src/storageusermanager.js delete mode 100644 packages/auth/src/token.js delete mode 100644 packages/auth/src/universallinksubscriber.js delete mode 100644 packages/auth/src/userevent.js delete mode 100644 packages/auth/src/utils.js delete mode 100644 packages/auth/test/actioncodeinfo_test.js delete mode 100644 packages/auth/test/actioncodesettings_test.js delete mode 100644 packages/auth/test/actioncodeurl_test.js delete mode 100644 packages/auth/test/additionaluserinfo_test.js delete mode 100644 packages/auth/test/args_test.js delete mode 100644 packages/auth/test/auth_test.js delete mode 100644 packages/auth/test/authcredential_test.js delete mode 100644 packages/auth/test/authevent_test.js delete mode 100644 packages/auth/test/autheventmanager_test.js delete mode 100644 packages/auth/test/authsettings_test.js delete mode 100644 packages/auth/test/authstorage_test.js delete mode 100644 packages/auth/test/authuser_test.js delete mode 100644 packages/auth/test/cacherequest_test.js delete mode 100644 packages/auth/test/confirmationresult_test.js delete mode 100644 packages/auth/test/cordovahandler_test.js delete mode 100644 packages/auth/test/defines_test.js delete mode 100644 packages/auth/test/deprecation_test.js delete mode 100644 packages/auth/test/dynamiclink_test.js delete mode 100644 packages/auth/test/error_test.js delete mode 100644 packages/auth/test/exports_lib_test.js rename {packages-exp/auth-exp => packages/auth}/test/helpers/api/helper.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/delay.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/fake_service_worker.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/id_token_response.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/iframe_event.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/integration/emulator_rest_helpers.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/integration/helpers.ts (97%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/integration/settings.ts (97%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/jwt.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/mock_auth.ts (98%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/mock_auth_credential.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/mock_fetch.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/mock_fetch.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/mock_popup_redirect_resolver.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/redirect_persistence.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/helpers/timeout_stub.ts (100%) delete mode 100644 packages/auth/test/idp_test.js delete mode 100644 packages/auth/test/idtoken_test.js delete mode 100644 packages/auth/test/idtokenresult_test.js delete mode 100644 packages/auth/test/iframeclient/ifchandler_test.js delete mode 100644 packages/auth/test/iframeclient/iframewrapper_test.js rename {packages-exp/auth-exp => packages/auth}/test/integration/flows/anonymous.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/flows/custom.local.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/flows/email.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/flows/idp.local.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/flows/oob.local.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/flows/phone.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/anonymous.test.ts (97%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/compat/firebaseui.test.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/persistence.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/popup.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/redirect.test.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/static/anonymous.js (92%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/static/core.js (100%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/static/email.js (91%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/static/index.html (100%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/static/index.js (95%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/static/persistence.js (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/static/popup.js (98%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/static/redirect.js (98%) rename {packages-exp/auth-compat-exp => packages/auth}/test/integration/webdriver/static/rollup.config.js (94%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/util/auth_driver.ts (99%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/util/functions.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/util/idp_page.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/util/js_load_condition.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/util/test_runner.ts (100%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/util/test_server.ts (96%) rename {packages-exp/auth-exp => packages/auth}/test/integration/webdriver/util/ui_page.ts (100%) delete mode 100644 packages/auth/test/messagechannel/postmessager_test.js delete mode 100644 packages/auth/test/messagechannel/receiver_test.js delete mode 100644 packages/auth/test/messagechannel/sender_test.js delete mode 100644 packages/auth/test/multifactorassertion_test.js delete mode 100644 packages/auth/test/multifactorerror_test.js delete mode 100644 packages/auth/test/multifactorgenerator_test.js delete mode 100644 packages/auth/test/multifactorinfo_test.js delete mode 100644 packages/auth/test/multifactorresolver_test.js delete mode 100644 packages/auth/test/multifactorsession_test.js delete mode 100644 packages/auth/test/multifactoruser_test.js delete mode 100644 packages/auth/test/oauthhelperstate_test.js delete mode 100644 packages/auth/test/object_test.js delete mode 100644 packages/auth/test/proactiverefresh_test.js delete mode 100644 packages/auth/test/recaptchaverifier/grecaptchamock_test.js delete mode 100644 packages/auth/test/recaptchaverifier/mockloader_test.js delete mode 100644 packages/auth/test/recaptchaverifier/realloader_test.js delete mode 100644 packages/auth/test/recaptchaverifier/recaptchaverifier_test.js delete mode 100644 packages/auth/test/rpchandler_test.js delete mode 100644 packages/auth/test/storage/asyncstorage_test.js delete mode 100644 packages/auth/test/storage/factory_test.js delete mode 100644 packages/auth/test/storage/hybridindexeddb_test.js delete mode 100644 packages/auth/test/storage/indexeddb_test.js delete mode 100644 packages/auth/test/storage/inmemorystorage_test.js delete mode 100644 packages/auth/test/storage/localstorage_test.js delete mode 100644 packages/auth/test/storage/mockstorage_test.js delete mode 100644 packages/auth/test/storage/nullstorage_test.js delete mode 100644 packages/auth/test/storage/sessionstorage_test.js delete mode 100644 packages/auth/test/storage/testhelper.js delete mode 100644 packages/auth/test/storageautheventmanager_test.js delete mode 100644 packages/auth/test/storageoauthhandlermanager_test.js delete mode 100644 packages/auth/test/storagependingredirectmanager_test.js delete mode 100644 packages/auth/test/storageredirectusermanager_test.js delete mode 100644 packages/auth/test/storageusermanager_test.js delete mode 100644 packages/auth/test/testhelper.js delete mode 100644 packages/auth/test/token_test.js delete mode 100644 packages/auth/test/universallinksubscriber_test.js delete mode 100644 packages/auth/test/userevent_test.js delete mode 100644 packages/auth/test/utils_test.js rename {packages-exp/auth-exp => packages/auth}/tsconfig.json (100%) rename {packages-exp/functions-exp => packages/database-compat}/.eslintrc.js (53%) rename {packages-exp/app-exp => packages/database-compat}/README.md (56%) rename {packages-exp/functions-exp => packages/database-compat}/karma.conf.js (94%) create mode 100644 packages/database-compat/package.json rename packages/{database/rollup.config.exp.js => database-compat/rollup.config.js} (75%) create mode 100644 packages/database-compat/src/api/Database.ts create mode 100644 packages/database-compat/src/api/Reference.ts rename packages/{database => database-compat}/src/api/TransactionResult.ts (100%) rename packages/{database => database-compat}/src/api/internal.ts (55%) rename packages/{database => database-compat}/src/api/onDisconnect.ts (85%) rename packages/{database/compat => database-compat/src}/index.node.ts (88%) rename packages/{database/compat => database-compat/src}/index.ts (81%) create mode 100644 packages/database-compat/src/util/util.ts create mode 100644 packages/database-compat/src/util/validation.ts rename packages/{database => database-compat}/test/browser/crawler_support.test.ts (98%) rename packages/{database => database-compat}/test/database.test.ts (99%) rename packages/{database => database-compat}/test/datasnapshot.test.ts (97%) rename packages/{database => database-compat}/test/helpers/events.ts (99%) create mode 100644 packages/database-compat/test/helpers/util.ts rename packages/{database => database-compat}/test/info.test.ts (98%) rename packages/{database => database-compat}/test/order.test.ts (99%) rename packages/{database => database-compat}/test/order_by.test.ts (99%) rename packages/{database => database-compat}/test/promise.test.ts (100%) rename packages/{database => database-compat}/test/query.test.ts (99%) rename packages/{database => database-compat}/test/servervalues.test.ts (100%) rename packages/{database => database-compat}/test/transaction.test.ts (99%) rename {packages-exp/app-exp => packages/database-compat}/tsconfig.json (88%) delete mode 100644 packages/database/.npmignore delete mode 100644 packages/database/compat/package.json delete mode 100644 packages/database/exp/package.json delete mode 100644 packages/database/index.node.ts delete mode 100644 packages/database/index.ts delete mode 100644 packages/database/rollup.config.compat.js rename packages/database/{exp => src}/api.ts (58%) rename packages/database/src/{exp => api}/OnDisconnect.ts (100%) rename packages/database/src/{exp => api}/Reference_impl.ts (100%) rename packages/database/src/{exp => api}/ServerValue.ts (100%) rename packages/database/src/{exp => api}/Transaction.ts (100%) delete mode 100644 packages/database/src/exp/Database.ts delete mode 100644 packages/database/src/exp/Reference.ts rename packages/database/{exp => src}/index.node.ts (100%) rename packages/database/{exp => src}/index.ts (83%) rename packages/database/{exp => src}/register.ts (82%) rename {packages-exp/app-exp => packages/firebase}/.eslintrc.js (100%) rename {packages-exp/firebase-exp => packages/firebase}/app/index.cdn.ts (88%) rename {packages-exp/firebase-exp/app-check => packages/firebase/auth/cordova}/index.ts (93%) create mode 100644 packages/firebase/auth/cordova/package.json rename {packages-exp/firebase-exp/auth/cordova => packages/firebase/auth/react-native}/index.ts (93%) create mode 100644 packages/firebase/auth/react-native/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/analytics/index.ts (100%) create mode 100644 packages/firebase/compat/analytics/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/app-check/index.ts (100%) create mode 100644 packages/firebase/compat/app-check/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/app/index.cdn.ts (100%) rename {packages-exp/firebase-exp => packages/firebase}/compat/app/index.ts (100%) create mode 100644 packages/firebase/compat/app/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/auth/index.ts (100%) create mode 100644 packages/firebase/compat/auth/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/database/index.ts (100%) create mode 100644 packages/firebase/compat/database/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/firestore/index.ts (100%) create mode 100644 packages/firebase/compat/firestore/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/functions/index.ts (100%) create mode 100644 packages/firebase/compat/functions/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/index.cdn.ts (100%) rename packages/firebase/{ => compat}/index.d.ts (100%) rename {packages-exp/firebase-exp => packages/firebase}/compat/index.node.ts (100%) rename {packages-exp/firebase-exp => packages/firebase}/compat/index.perf.ts (100%) rename {packages-exp/firebase-exp => packages/firebase}/compat/index.rn.ts (100%) rename {packages-exp/firebase-exp => packages/firebase}/compat/index.ts (100%) rename {packages-exp/firebase-exp => packages/firebase}/compat/messaging/index.ts (100%) create mode 100644 packages/firebase/compat/messaging/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/package.json (78%) rename {packages-exp/firebase-exp => packages/firebase}/compat/performance/index.ts (100%) create mode 100644 packages/firebase/compat/performance/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/remote-config/index.ts (100%) create mode 100644 packages/firebase/compat/remote-config/package.json rename {packages-exp/firebase-exp => packages/firebase}/compat/rollup.config.js (79%) rename {packages-exp/firebase-exp => packages/firebase}/compat/storage/index.ts (100%) create mode 100644 packages/firebase/compat/storage/package.json delete mode 100644 packages/firebase/empty-import.d.ts delete mode 100644 packages/firebase/externs/firebase-app-externs.js delete mode 100644 packages/firebase/externs/firebase-app-internal-externs.js delete mode 100644 packages/firebase/externs/firebase-auth-externs.js delete mode 100644 packages/firebase/externs/firebase-client-auth-externs.js delete mode 100644 packages/firebase/externs/firebase-database-externs.js delete mode 100644 packages/firebase/externs/firebase-database-internal-externs.js delete mode 100644 packages/firebase/externs/firebase-error-externs.js delete mode 100644 packages/firebase/externs/firebase-externs.js delete mode 100644 packages/firebase/externs/firebase-firestore-externs.js delete mode 100644 packages/firebase/externs/firebase-messaging-externs.js delete mode 100644 packages/firebase/externs/firebase-storage-externs.js delete mode 100644 packages/firebase/firestore/bundle/index.ts delete mode 100644 packages/firebase/firestore/bundle/package.json delete mode 100644 packages/firebase/firestore/index.cdn.ts rename {packages-exp/firebase-exp => packages/firebase}/firestore/lite/index.ts (100%) create mode 100644 packages/firebase/firestore/lite/package.json delete mode 100644 packages/firebase/firestore/memory/bundle/index.ts delete mode 100644 packages/firebase/firestore/memory/bundle/package.json delete mode 100644 packages/firebase/firestore/memory/index.cdn.ts delete mode 100644 packages/firebase/firestore/memory/index.ts delete mode 100644 packages/firebase/firestore/memory/package.json rename {packages-exp/firebase-exp => packages/firebase}/gulpfile.js (71%) delete mode 100644 packages/firebase/index.html delete mode 100644 packages/firebase/installations/index.ts delete mode 100644 packages/firebase/installations/package.json rename {packages-exp/firebase-exp/firestore => packages/firebase/messaging/sw}/index.ts (93%) create mode 100644 packages/firebase/messaging/sw/package.json delete mode 100644 packages/firebase/rollup-internal.config.js delete mode 100644 packages/firebase/src/index.cdn.ts delete mode 100644 packages/firebase/src/index.node.ts delete mode 100644 packages/firebase/src/index.perf.ts delete mode 100644 packages/firebase/src/index.rn.ts delete mode 100644 packages/firebase/src/index.ts create mode 100644 packages/firestore-compat/.eslintrc.js create mode 100644 packages/firestore-compat/README.md create mode 100644 packages/firestore-compat/karma.conf.js create mode 100644 packages/firestore-compat/package.json create mode 100644 packages/firestore-compat/rollup.config.js rename packages/{firestore => firestore-compat}/src/api/blob.ts (89%) create mode 100644 packages/firestore-compat/src/api/database.ts create mode 100644 packages/firestore-compat/src/api/field_path.ts create mode 100644 packages/firestore-compat/src/api/field_value.ts rename packages/{firestore/src/exp => firestore-compat/src/api}/geo_point.ts (88%) create mode 100644 packages/firestore-compat/src/api/observer.ts rename packages/{firestore/src/exp => firestore-compat/src/api}/timestamp.ts (88%) rename packages/{firestore/compat => firestore-compat/src}/config.ts (80%) rename packages/{firestore => firestore-compat/src}/index.console.ts (72%) rename packages/{firestore/compat => firestore-compat/src}/index.node.ts (78%) rename packages/{firestore/compat => firestore-compat/src}/index.rn.ts (78%) rename packages/{firestore/compat => firestore-compat/src}/index.ts (85%) rename packages/{firestore => firestore-compat/src}/register-module.ts (97%) rename packages-exp/app-check-exp/karma.conf.js => packages/firestore-compat/src/util/input_validation.ts (51%) rename packages/{firestore => firestore-compat}/tools/console.build.js (56%) rename packages/{polyfill => firestore-compat}/tsconfig.json (100%) create mode 100644 packages/firestore/.gitignore delete mode 100644 packages/firestore/bundle/package.json delete mode 100644 packages/firestore/compat/bundle.ts delete mode 100644 packages/firestore/compat/package.json delete mode 100644 packages/firestore/exp/package.json delete mode 100644 packages/firestore/export.ts delete mode 100644 packages/firestore/index.bundle.ts delete mode 100644 packages/firestore/index.memory.ts delete mode 100644 packages/firestore/index.node.memory.ts delete mode 100644 packages/firestore/index.node.ts delete mode 100644 packages/firestore/index.rn.memory.ts delete mode 100644 packages/firestore/index.rn.ts delete mode 100644 packages/firestore/index.ts delete mode 100644 packages/firestore/memory-bundle/package.json delete mode 100644 packages/firestore/memory/package.json delete mode 100644 packages/firestore/rollup.config.browser.compat.js delete mode 100644 packages/firestore/rollup.config.browser.js rename packages/firestore/{rollup.config.exp.js => rollup.config.js} (79%) delete mode 100644 packages/firestore/rollup.config.node.compat.js delete mode 100644 packages/firestore/rollup.config.node.js delete mode 100644 packages/firestore/rollup.config.rn.compat.js delete mode 100644 packages/firestore/rollup.config.rn.js create mode 100644 packages/firestore/scripts/prepare-test.js create mode 100644 packages/firestore/scripts/prepare-test.ts rename packages/firestore/{exp => src}/api.ts (54%) rename packages/firestore/src/{exp => api}/bundle.ts (100%) rename packages/firestore/src/{exp => api}/bytes.ts (93%) rename packages/firestore/src/{exp => api}/field_value_impl.ts (94%) rename packages/firestore/src/{exp => api}/query.ts (96%) rename packages/firestore/src/{exp => api}/reference.ts (96%) rename packages/firestore/src/{exp => api}/reference_impl.ts (98%) rename packages/firestore/src/{exp => api}/settings.ts (96%) rename packages/firestore/src/{exp => api}/snapshot.ts (98%) rename packages/firestore/src/{exp => api}/transaction.ts (94%) rename packages/firestore/src/{exp => api}/write_batch.ts (96%) delete mode 100644 packages/firestore/src/config.ts delete mode 100644 packages/firestore/src/exp/database.ts delete mode 100644 packages/firestore/src/exp/field_path.ts delete mode 100644 packages/firestore/src/exp/field_value.ts rename packages/firestore/{exp => src}/index.node.ts (100%) rename packages/firestore/{exp => src}/index.rn.ts (100%) rename packages/firestore/{exp => src}/index.ts (83%) rename packages/firestore/src/{lite => lite-api}/bytes.ts (100%) rename packages/firestore/src/{lite => lite-api}/components.ts (98%) rename packages/firestore/src/{lite => lite-api}/database.ts (95%) rename packages/firestore/src/{lite => lite-api}/field_path.ts (100%) rename packages/firestore/src/{lite => lite-api}/field_value.ts (100%) rename packages/firestore/src/{lite => lite-api}/field_value_impl.ts (100%) rename packages/firestore/src/{lite => lite-api}/geo_point.ts (100%) rename packages/firestore/src/{lite => lite-api}/query.ts (100%) rename packages/firestore/src/{lite => lite-api}/reference.ts (100%) rename packages/firestore/src/{lite => lite-api}/reference_impl.ts (100%) rename packages/firestore/src/{lite => lite-api}/settings.ts (100%) rename packages/firestore/src/{lite => lite-api}/snapshot.ts (100%) rename packages/firestore/src/{lite => lite-api}/timestamp.ts (100%) rename packages/firestore/src/{lite => lite-api}/transaction.ts (100%) rename packages/firestore/src/{lite => lite-api}/types.ts (100%) rename packages/firestore/src/{lite => lite-api}/user_data_reader.ts (100%) rename packages/firestore/src/{lite => lite-api}/user_data_writer.ts (98%) rename packages/firestore/src/{lite => lite-api}/write_batch.ts (100%) rename packages/firestore/{exp => src}/register.ts (77%) create mode 100644 packages/firestore/test/register.ts rename {packages-exp => packages}/functions-compat/.eslintrc.js (100%) rename {packages-exp => packages}/functions-compat/karma.conf.js (100%) rename {packages-exp => packages}/functions-compat/package.json (85%) create mode 100644 packages/functions-compat/rollup.config.js rename {packages-exp => packages}/functions-compat/src/callable.test.ts (96%) rename {packages-exp => packages}/functions-compat/src/index.node.ts (100%) rename {packages-exp => packages}/functions-compat/src/index.ts (100%) rename {packages-exp => packages}/functions-compat/src/register.ts (82%) rename {packages-exp => packages}/functions-compat/src/service.test.ts (98%) rename {packages-exp => packages}/functions-compat/src/service.ts (98%) rename {packages-exp => packages}/functions-compat/test/utils.ts (95%) rename {packages-exp/firebase-exp => packages/functions-compat}/tsconfig.json (100%) delete mode 100644 packages/functions/.npmignore rename {packages-exp/functions-exp => packages/functions}/api-extractor.json (100%) delete mode 100644 packages/functions/index.node.ts delete mode 100644 packages/functions/index.ts rename {packages-exp/functions-exp => packages/functions}/src/api.ts (97%) delete mode 100644 packages/functions/src/api/error.ts delete mode 100644 packages/functions/src/api/service.ts rename {packages-exp/functions-exp => packages/functions}/src/callable.test.ts (99%) rename {packages-exp/functions-exp => packages/functions}/src/constants.ts (93%) rename {packages-exp/functions-exp => packages/functions}/src/error.ts (100%) rename {packages-exp/functions-exp => packages/functions}/src/index.node.ts (94%) rename {packages-exp/functions-exp => packages/functions}/src/index.ts (94%) rename {packages-exp/functions-exp => packages/functions}/src/public-types.ts (98%) rename {packages-exp/functions-exp => packages/functions}/src/serializer.test.ts (100%) rename {packages-exp/functions-exp => packages/functions}/src/service.test.ts (100%) rename {packages-exp/functions-exp => packages/functions}/src/service.ts (99%) delete mode 100644 packages/functions/test/browser/callable.test.ts delete mode 100644 packages/functions/test/callable.test.ts delete mode 100644 packages/functions/test/serializer.test.ts delete mode 100644 packages/functions/test/service.test.ts rename {packages-exp/firebase-exp => packages/installations-compat}/.eslintrc.js (100%) create mode 100644 packages/installations-compat/README.md rename {packages-exp => packages}/installations-compat/karma.conf.js (100%) rename {packages-exp => packages}/installations-compat/package.json (94%) rename packages-exp/installations-compat/rollup.shared.js => packages/installations-compat/rollup.config.js (67%) rename {packages-exp => packages}/installations-compat/src/index.ts (97%) rename {packages-exp => packages}/installations-compat/src/installationsCompat.test.ts (97%) rename {packages-exp => packages}/installations-compat/src/installationsCompat.ts (97%) rename {packages-exp => packages}/installations-compat/src/testing/setup.ts (100%) rename {packages-exp => packages}/installations-compat/src/testing/util.ts (94%) rename {packages-exp => packages}/installations-compat/tsconfig.json (100%) rename {packages-exp/installations-exp => packages/installations}/api-extractor.json (100%) delete mode 100644 packages/installations/src/api/common.test.ts delete mode 100644 packages/installations/src/api/common.ts delete mode 100644 packages/installations/src/api/create-installation-request.test.ts delete mode 100644 packages/installations/src/api/create-installation-request.ts delete mode 100644 packages/installations/src/api/delete-installation-request.test.ts delete mode 100644 packages/installations/src/api/delete-installation-request.ts rename {packages-exp/installations-exp => packages/installations}/src/api/delete-installations.test.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/api/delete-installations.ts (100%) delete mode 100644 packages/installations/src/api/generate-auth-token-request.test.ts delete mode 100644 packages/installations/src/api/generate-auth-token-request.ts rename {packages-exp/installations-exp => packages/installations}/src/api/get-id.test.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/api/get-id.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/api/get-installations.ts (97%) rename {packages-exp/installations-exp => packages/installations}/src/api/get-token.test.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/api/get-token.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/api/index.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/api/on-id-change.test.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/api/on-id-change.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/functions/common.test.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/functions/common.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/functions/config.ts (82%) rename {packages-exp/installations-exp => packages/installations}/src/functions/create-installation-request.test.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/functions/create-installation-request.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/functions/delete-installation-request.test.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/functions/delete-installation-request.ts (100%) delete mode 100644 packages/installations/src/functions/delete-installation.test.ts delete mode 100644 packages/installations/src/functions/delete-installation.ts rename {packages-exp/installations-exp => packages/installations}/src/functions/generate-auth-token-request.test.ts (100%) rename {packages-exp/installations-exp => packages/installations}/src/functions/generate-auth-token-request.ts (100%) delete mode 100644 packages/installations/src/functions/get-id.test.ts delete mode 100644 packages/installations/src/functions/get-id.ts delete mode 100644 packages/installations/src/functions/get-token.test.ts delete mode 100644 packages/installations/src/functions/get-token.ts delete mode 100644 packages/installations/src/functions/index.ts delete mode 100644 packages/installations/src/functions/on-id-change.test.ts delete mode 100644 packages/installations/src/functions/on-id-change.ts delete mode 100644 packages/installations/src/interfaces/app-config.ts delete mode 100644 packages/installations/src/interfaces/firebase-dependencies.ts rename {packages-exp/installations-exp => packages/installations}/src/interfaces/installation-impl.ts (95%) rename {packages-exp/installations-exp => packages/installations}/src/interfaces/public-types.ts (80%) rename {packages-exp => packages}/messaging-compat/.eslintrc.js (100%) rename {packages-exp => packages}/messaging-compat/README.md (100%) rename {packages-exp => packages}/messaging-compat/karma.conf.js (100%) rename {packages-exp => packages}/messaging-compat/package.json (81%) rename {packages-exp => packages}/messaging-compat/rollup.config.js (100%) rename {packages-exp => packages}/messaging-compat/src/index.ts (100%) rename {packages-exp => packages}/messaging-compat/src/messaging-compat.ts (96%) rename {packages-exp => packages}/messaging-compat/src/registerMessagingCompat.ts (90%) rename {packages-exp => packages}/messaging-compat/test/fakes.ts (95%) rename {packages-exp => packages}/messaging-compat/test/messaging-compat.test.ts (93%) rename {packages-exp => packages}/messaging-compat/tsconfig.json (100%) delete mode 100644 packages/messaging/.npmignore rename {packages-exp/messaging-exp => packages/messaging}/api-extractor.json (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/api.ts (77%) rename {packages-exp/messaging-exp => packages/messaging}/src/api/deleteToken.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/api/getToken.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/api/isSupported.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/api/onBackgroundMessage.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/api/onMessage.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/api/setDeliveryMetricsExportedToBigQueryEnabled.ts (100%) delete mode 100644 packages/messaging/src/controllers/sw-controller.test.ts delete mode 100644 packages/messaging/src/controllers/sw-controller.ts delete mode 100644 packages/messaging/src/controllers/window-controller.test.ts delete mode 100644 packages/messaging/src/controllers/window-controller.ts delete mode 100644 packages/messaging/src/core/api.test.ts delete mode 100644 packages/messaging/src/core/api.ts delete mode 100644 packages/messaging/src/core/token-management.test.ts delete mode 100644 packages/messaging/src/core/token-management.ts delete mode 100644 packages/messaging/src/helpers/idb-manager.test.ts delete mode 100644 packages/messaging/src/helpers/idb-manager.ts rename {packages-exp/messaging-exp => packages/messaging}/src/helpers/logToFirelog.test.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/helpers/logToFirelog.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/helpers/logToScion.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/helpers/register.ts (59%) rename {packages-exp/messaging-exp => packages/messaging}/src/helpers/registerDefaultSw.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/helpers/updateSwReg.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/helpers/updateVapidKey.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/index.sw.ts (90%) rename {packages-exp/messaging-exp => packages/messaging}/src/interfaces/logging-types.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/interfaces/public-types.ts (94%) rename {packages-exp/messaging-exp => packages/messaging}/src/internals/idb-manager.test.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/internals/idb-manager.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/internals/requests.test.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/internals/requests.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/internals/token-manager.test.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/internals/token-manager.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/listeners/sw-listeners.test.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/listeners/sw-listeners.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/listeners/window-listener.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/messaging-service.ts (96%) rename {packages-exp/messaging-exp => packages/messaging}/src/testing/fakes/logging-object.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/src/testing/fakes/messaging-service.ts (100%) rename {packages-exp/messaging-exp => packages/messaging}/sw/package.json (83%) rename {packages-exp/installations-compat => packages/performance-compat}/.eslintrc.js (100%) rename {packages-exp => packages}/performance-compat/README.md (100%) rename {packages-exp => packages}/performance-compat/karma.conf.js (100%) rename {packages-exp => packages}/performance-compat/package.json (86%) rename {packages-exp/app-exp => packages/performance-compat}/rollup.config.js (61%) rename {packages-exp => packages}/performance-compat/src/index.ts (86%) rename {packages-exp => packages}/performance-compat/src/performance.test.ts (97%) rename {packages-exp => packages}/performance-compat/src/performance.ts (97%) rename {packages-exp/app-exp => packages/performance-compat}/test/setup.ts (100%) rename {packages-exp => packages}/performance-compat/test/util.ts (97%) rename {packages-exp => packages}/performance-compat/tsconfig.json (100%) rename {packages-exp/performance-exp => packages/performance}/api-extractor.json (100%) delete mode 100644 packages/performance/index.ts rename {packages-exp/performance-exp => packages/performance}/src/index.test.ts (99%) rename {packages-exp/performance-exp => packages/performance}/src/index.ts (90%) rename {packages-exp/performance-exp => packages/performance}/src/public_types.ts (94%) rename {packages-exp/performance-exp => packages/performance}/src/utils/app_utils.ts (96%) delete mode 100644 packages/polyfill/README.md delete mode 100644 packages/polyfill/index.ts delete mode 100644 packages/polyfill/package.json delete mode 100644 packages/polyfill/rollup.config.js rename {packages-exp/installations-exp => packages/remote-config-compat}/.eslintrc.js (100%) rename {packages-exp => packages}/remote-config-compat/README.md (77%) rename {packages-exp => packages}/remote-config-compat/karma.conf.js (100%) rename {packages-exp => packages}/remote-config-compat/package.json (81%) rename packages-exp/performance-compat/rollup.shared.js => packages/remote-config-compat/rollup.config.js (61%) rename {packages-exp => packages}/remote-config-compat/src/index.ts (87%) rename {packages-exp => packages}/remote-config-compat/src/remoteConfig.test.ts (98%) rename {packages-exp => packages}/remote-config-compat/src/remoteConfig.ts (98%) rename {packages-exp/performance-compat => packages/remote-config-compat}/test/setup.ts (100%) rename {packages-exp => packages}/remote-config-compat/test/util.ts (95%) rename {packages-exp => packages}/remote-config-compat/tsconfig.json (100%) rename {packages-exp/remote-config-exp => packages/remote-config}/api-extractor.json (100%) delete mode 100644 packages/remote-config/index.ts rename {packages-exp/remote-config-exp => packages/remote-config}/src/api.ts (99%) rename {packages-exp/remote-config-exp => packages/remote-config}/src/api2.ts (100%) rename packages-exp/firebase-exp/database/index.ts => packages/remote-config/src/constants.ts (92%) rename {packages-exp/remote-config-exp => packages/remote-config}/src/index.ts (100%) rename {packages-exp/remote-config-exp => packages/remote-config}/src/public_types.ts (94%) rename {packages-exp/remote-config-exp => packages/remote-config}/src/register.ts (95%) rename packages-exp/analytics-exp/src/logger.ts => packages/rules-unit-testing/mocharc.node.js (78%) delete mode 100644 packages/rules-unit-testing/src/api/index.ts create mode 100644 packages/rules-unit-testing/src/impl/discovery.ts create mode 100644 packages/rules-unit-testing/src/impl/rules.ts create mode 100644 packages/rules-unit-testing/src/impl/test_environment.ts create mode 100644 packages/rules-unit-testing/src/impl/url.ts create mode 100644 packages/rules-unit-testing/src/initialize.ts create mode 100644 packages/rules-unit-testing/src/public_types/index.ts create mode 100644 packages/rules-unit-testing/src/util.ts delete mode 100644 packages/rules-unit-testing/test/database.test.ts rename packages-exp/auth-exp/rollup.config.js => packages/rules-unit-testing/test/dummy.test.ts (79%) create mode 100644 packages/rules-unit-testing/test/impl/discovery.test.ts create mode 100644 packages/rules-unit-testing/test/impl/rules.test.ts rename {packages-exp/performance-exp => packages/rules-unit-testing}/test/setup.ts (88%) rename packages-exp/app-check-exp/.eslintrc.js => packages/rules-unit-testing/test/test_utils.ts (52%) create mode 100644 packages/rules-unit-testing/test/util.test.ts rename {packages-exp/performance-exp => packages/storage-compat}/.eslintrc.js (83%) rename {packages-exp/analytics-exp => packages/storage-compat}/README.md (65%) rename {packages-exp/remote-config-exp => packages/storage-compat}/karma.conf.js (67%) create mode 100644 packages/storage-compat/package.json rename packages/{storage/rollup.config.compat.js => storage-compat/rollup.config.js} (59%) rename packages/{storage/compat => storage-compat/src}/index.ts (90%) rename packages/{storage/compat => storage-compat/src}/list.ts (96%) rename packages/{storage/compat => storage-compat/src}/reference.ts (92%) rename packages/{storage/compat => storage-compat/src}/service.ts (86%) rename packages/{storage/compat => storage-compat/src}/task.ts (99%) rename packages/{storage/compat => storage-compat/src}/tasksnapshot.ts (95%) rename packages/{storage/test/integration/integration.compat.test.ts => storage-compat/test/integration/integration.test.ts} (96%) rename {packages-exp/app-check-exp => packages/storage-compat}/test/setup.ts (96%) rename packages/{storage/test/unit/index.compat.test.ts => storage-compat/test/unit/index.test.ts} (84%) create mode 100644 packages/storage-compat/test/unit/reference.test.ts create mode 100644 packages/storage-compat/test/unit/service.test.ts rename packages/{storage/rollup.shared.js => storage-compat/test/utils.ts} (54%) rename {packages-exp/functions-compat => packages/storage-compat}/tsconfig.json (100%) delete mode 100644 packages/storage/.npmignore delete mode 100644 packages/storage/compat/package.json delete mode 100644 packages/storage/exp/package.json delete mode 100644 packages/storage/index.ts delete mode 100644 packages/storage/register-module.ts delete mode 100644 packages/storage/rollup.config.exp.js rename packages/storage/{exp => src}/api.ts (91%) rename packages/storage/{exp => src}/constants.ts (93%) rename packages/storage/{exp => src}/index.ts (95%) rename packages/storage/{exp => src}/public-types.ts (95%) delete mode 100644 packages/storage/src/tasksnapshot.ts rename packages/storage/test/integration/{integration.exp.test.ts => integration.test.ts} (96%) rename packages/storage/test/unit/{index.exp.test.ts => index.test.ts} (94%) delete mode 100644 packages/storage/test/unit/reference.compat.test.ts rename packages/storage/test/unit/{reference.exp.test.ts => reference.test.ts} (100%) delete mode 100644 packages/storage/test/unit/service.compat.test.ts rename packages/storage/test/unit/{service.exp.test.ts => service.test.ts} (94%) delete mode 100644 scripts/exp/update-api-reports.ts diff --git a/.changeset/config.json b/.changeset/config.json index 8a7e96564c6..d81b9ddee07 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -15,25 +15,6 @@ "firebase-messaging-integration-test", "firebase-compat-interop-test", "firebase-compat-typings-test", - "@firebase/app-compat", - "@firebase/app-exp", - "@firebase/app-check-compat", - "@firebase/app-check-exp", - "@firebase/analytics-compat", - "@firebase/analytics-exp", - "@firebase/auth-exp", - "@firebase/auth-compat", - "@firebase/functions-compat", - "@firebase/functions-exp", - "@firebase/installations-exp", - "@firebase/installations-compat", - "@firebase/messaging-exp", - "@firebase/messaging-compat", - "@firebase/performance-exp", - "@firebase/performance-compat", - "@firebase/remote-config-exp", - "@firebase/remote-config-compat", - "firebase-exp", "@firebase/changelog-generator", "firebase-size-analysis" ], diff --git a/.changeset/stale-ducks-live.md b/.changeset/stale-ducks-live.md new file mode 100644 index 00000000000..a01da06e64e --- /dev/null +++ b/.changeset/stale-ducks-live.md @@ -0,0 +1,5 @@ +--- +"@firebase/rules-unit-testing": major +--- + +BREAKING: Implement Rules Unit Testing v2 with new design and APIs. diff --git a/.changeset/tame-olives-compete.md b/.changeset/tame-olives-compete.md new file mode 100644 index 00000000000..36f590478d0 --- /dev/null +++ b/.changeset/tame-olives-compete.md @@ -0,0 +1,43 @@ +--- +'firebase': major +'@firebase/firestore': major +'@firebase/rules-unit-testing': major +'@firebase/firestore-compat': minor +'@firebase/firestore-types': minor +'@firebase/analytics': minor +'@firebase/analytics-compat': minor +'@firebase/analytics-types': minor +'@firebase/app': minor +'@firebase/app-check': minor +'@firebase/app-check-compat': minor +'@firebase/app-check-types': minor +'@firebase/app-compat': minor +'@firebase/app-types': minor +'@firebase/auth': minor +'@firebase/auth-compat': minor +'@firebase/auth-types': minor +'@firebase/database': minor +'@firebase/database-compat': minor +'@firebase/database-types': minor +'@firebase/functions': minor +'@firebase/functions-compat': minor +'@firebase/functions-types': minor +'@firebase/installations': minor +'@firebase/installations-compat': minor +'@firebase/installations-types': minor +'@firebase/messaging': minor +'@firebase/messaging-compat': minor +'@firebase/messaging-types': minor +'@firebase/messaging-interop-types': minor +'@firebase/performance': minor +'@firebase/performance-compat': minor +'@firebase/performance-types': minor +'@firebase/remote-config': minor +'@firebase/remote-config-compat': minor +'@firebase/remote-config-types': minor +'@firebase/storage': minor +'@firebase/storage-compat': minor +'@firebase/storage-types': minor +--- + +Release modularized SDKs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7a04d4eea2c..7c636467a7f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -35,6 +35,7 @@ # Database Code packages/database @schmidt-sebastian @jsdt @firebase/jssdk-global-approvers +packages/database-compat @schmidt-sebastian @jsdt @firebase/jssdk-global-approvers packages/database-types @schmidt-sebastian @jsdt @firebase/jssdk-global-approvers # Firestore Code @@ -45,56 +46,54 @@ integration/firestore @firebase/firestore-js-team @firebase/jssdk-global-approv # Storage Code packages/storage @schmidt-sebastian @firebase/jssdk-global-approvers +packages/storage-compat @schmidt-sebastian @firebase/jssdk-global-approvers packages/storage-types @schmidt-sebastian @firebase/jssdk-global-approvers # Messaging Code packages/messaging @zwu52 @chliangGoogle @ciarand @firebase/jssdk-global-approvers +packages/messaging-compat @zwu52 @chliangGoogle @ciarand @firebase/jssdk-global-approvers packages/messaging-types @zwu52 @chliangGoogle @ciarand @firebase/jssdk-global-approvers +packages/messaging-interop-types @zwu52 @chliangGoogle @ciarand @firebase/jssdk-global-approvers integration/messaging @zwu52 @chliangGoogle @ciarand @firebase/jssdk-global-approvers # Auth Code packages/auth @bojeil-google @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers +packages/auth-compat @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers packages/auth-types @bojeil-google @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers +packages/auth-interop-types @bojeil-google @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers # Testing Code -packages/testing @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers packages/rules-unit-testing @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers # Installations -packages/installations @andirayo @ChaoqunCHEN @firebase/jssdk-global-approvers -packages/installations-types @andirayo @ChaoqunCHEN @firebase/jssdk-global-approvers +packages/installations @avolkovi @yoyomyo @firebase/jssdk-global-approvers +packages/installations-compat @avolkovi @yoyomyo @firebase/jssdk-global-approvers +packages/installations-types @avolkovi @yoyomyo @firebase/jssdk-global-approvers # Performance Code -packages/performance @alikn @zijianjoy @firebase/jssdk-global-approvers -packages/performance-types @alikn @zijianjoy @firebase/jssdk-global-approvers +packages/performance @jposuna @firebase/jssdk-global-approvers +packages/performance-compat @jposuna @firebase/jssdk-global-approvers +packages/performance-types @jposuna @firebase/jssdk-global-approvers # Analytics Code packages/analytics @hsubox76 @Feiyang1 @firebase/jssdk-global-approvers +packages/analytics-compat @hsubox76 @Feiyang1 @firebase/jssdk-global-approvers packages/analytics-types @hsubox76 @Feiyang1 @firebase/jssdk-global-approvers # Remote Config Code packages/remote-config @erikeldridge @firebase/jssdk-global-approvers +packages/remote-config-compat @erikeldridge @firebase/jssdk-global-approvers packages/remote-config-types @erikeldridge @firebase/jssdk-global-approvers +# App Check Code +packages/app-check @hsubox76 @Feiyang1 @firebase/jssdk-global-approvers +packages/app-check-compat @hsubox76 @Feiyang1 @firebase/jssdk-global-approvers +packages/app-check-types @hsubox76 @Feiyang1 @firebase/jssdk-global-approvers +packages/app-check-interop-types @hsubox76 @Feiyang1 @firebase/jssdk-global-approvers + # Documentation Changes packages/firebase/index.d.ts @egilmorez @firebase/jssdk-global-approvers scripts/docgen/content-sources/ @egilmorez @firebase/jssdk-global-approvers # Changeset .changeset @egilmorez @firebase/jssdk-changeset-approvers @firebase/firestore-js-team @firebase/jssdk-global-approvers - -# Auth-Exp Code -packages-exp/auth-exp @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers -packages-exp/auth-compat-exp @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers - -# Installations-Exp Code -packages/installations-exp @avolkovi @yoyomyo @firebase/jssdk-global-approvers -packages/installations-types-exp @avolkovi @yoyomyo @firebase/jssdk-global-approvers - -# Perf-Exp Code -packages/performance-exp @alikn @zijianjoy @firebase/jssdk-global-approvers -packages/performance-types-exp @alikn @zijianjoy @firebase/jssdk-global-approvers - -# RC-Exp Code -packages/remote-config-exp @erikeldridge @firebase/jssdk-global-approvers -packages/remote-config-compat @erikeldridge @firebase/jssdk-global-approvers diff --git a/.github/workflows/test-changed-fcm-integration.yml b/.github/workflows/test-changed-fcm-integration.yml index 23d680cf6fe..94f74ab1428 100644 --- a/.github/workflows/test-changed-fcm-integration.yml +++ b/.github/workflows/test-changed-fcm-integration.yml @@ -28,6 +28,6 @@ jobs: cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed fcm-integration --buildAppExp + run: yarn build:changed fcm-integration - name: Run tests if FCM or its dependencies has changed run: xvfb-run yarn test:changed fcm-integration diff --git a/.github/workflows/test-changed-firestore-integration.yml b/.github/workflows/test-changed-firestore-integration.yml index b686021953d..da0c3ac0ad7 100644 --- a/.github/workflows/test-changed-firestore-integration.yml +++ b/.github/workflows/test-changed-firestore-integration.yml @@ -28,6 +28,6 @@ jobs: cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed firestore-integration --buildAppExp --buildAppCompat + run: yarn build:changed firestore-integration - name: Run tests if firestore or its dependencies has changed run: yarn test:changed firestore-integration diff --git a/.github/workflows/test-changed-firestore.yml b/.github/workflows/test-changed-firestore.yml index bc28ec6f4e0..fc56beb4ba0 100644 --- a/.github/workflows/test-changed-firestore.yml +++ b/.github/workflows/test-changed-firestore.yml @@ -28,6 +28,6 @@ jobs: cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed firestore --buildAppExp --buildAppCompat + run: yarn build:changed firestore - name: Run tests if firestore or its dependencies has changed run: yarn test:changed firestore diff --git a/.github/workflows/test-changed-misc.yml b/.github/workflows/test-changed-misc.yml index 13007a76cfb..aa45e2611ae 100644 --- a/.github/workflows/test-changed-misc.yml +++ b/.github/workflows/test-changed-misc.yml @@ -28,6 +28,6 @@ jobs: cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed misc --buildAppExp + run: yarn build:changed misc - name: Run tests run: yarn test:changed misc \ No newline at end of file diff --git a/.github/workflows/test-changed.yml b/.github/workflows/test-changed.yml index e818272a703..6f86e38e7ed 100644 --- a/.github/workflows/test-changed.yml +++ b/.github/workflows/test-changed.yml @@ -28,6 +28,6 @@ jobs: cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed core --buildAppExp + run: yarn build:changed core - name: Run tests on changed packages run: xvfb-run yarn test:changed core \ No newline at end of file diff --git a/.github/workflows/test-firebase-integration.yml b/.github/workflows/test-firebase-integration.yml index 154b2b7c12f..8b7be4057e0 100644 --- a/.github/workflows/test-firebase-integration.yml +++ b/.github/workflows/test-firebase-integration.yml @@ -28,6 +28,6 @@ jobs: cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed firebase-integration --buildAppExp + run: yarn build:changed firebase-integration - name: Run tests on changed packages run: yarn test:changed firebase-integration \ No newline at end of file diff --git a/.github/workflows/update-api-reports.yml b/.github/workflows/update-api-reports.yml index be30928e648..b47ac51170e 100644 --- a/.github/workflows/update-api-reports.yml +++ b/.github/workflows/update-api-reports.yml @@ -21,7 +21,8 @@ jobs: - name: Yarn install run: yarn - name: Update API reports - run: yarn ts-node-script scripts/exp/update-api-reports.ts + # API reports are generated as part of the build + run: yarn build id: update-api-reports - name: Commit & Push changes uses: EndBug/add-and-commit@v7 diff --git a/.gitignore b/.gitignore index dc9fe690372..80f8cb36d9d 100644 --- a/.gitignore +++ b/.gitignore @@ -84,10 +84,8 @@ package-lock.json # temp folder used by api-extractor temp -packages-exp/**/temp # temp markdowns generated for individual SDKs -packages-exp/**/docs packages/**/docs # files generated by api-extractor that should not be tracked diff --git a/common/api-review/analytics-exp.api.md b/common/api-review/analytics.api.md similarity index 96% rename from common/api-review/analytics-exp.api.md rename to common/api-review/analytics.api.md index b7f381967a0..091c3ffe7d3 100644 --- a/common/api-review/analytics-exp.api.md +++ b/common/api-review/analytics.api.md @@ -1,410 +1,410 @@ -## API Report File for "@firebase/analytics-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { FirebaseApp } from '@firebase/app-exp'; - -// @public -export interface Analytics { - app: FirebaseApp; -} - -// @public -export interface AnalyticsCallOptions { - global: boolean; -} - -// @public -export interface AnalyticsSettings { - config?: GtagConfigParams | EventParams; -} - -// @public -export interface ControlParams { - // (undocumented) - event_callback?: () => void; - // (undocumented) - event_timeout?: number; - // (undocumented) - groups?: string | string[]; - // (undocumented) - send_to?: string | string[]; -} - -// @public -export type Currency = string | number; - -// @public -export type CustomEventName = T extends EventNameString ? never : T; - -// @public -export interface CustomParams { - // (undocumented) - [key: string]: unknown; -} - -// @public -export type EventNameString = 'add_payment_info' | 'add_shipping_info' | 'add_to_cart' | 'add_to_wishlist' | 'begin_checkout' | 'checkout_progress' | 'exception' | 'generate_lead' | 'login' | 'page_view' | 'purchase' | 'refund' | 'remove_from_cart' | 'screen_view' | 'search' | 'select_content' | 'select_item' | 'select_promotion' | 'set_checkout_option' | 'share' | 'sign_up' | 'timing_complete' | 'view_cart' | 'view_item' | 'view_item_list' | 'view_promotion' | 'view_search_results'; - -// @public -export interface EventParams { - // (undocumented) - [key: string]: unknown; - // (undocumented) - affiliation?: string; - // (undocumented) - checkout_option?: string; - // (undocumented) - checkout_step?: number; - // (undocumented) - content_type?: string; - // (undocumented) - coupon?: string; - // (undocumented) - currency?: string; - // (undocumented) - description?: string; - // (undocumented) - event_category?: string; - // (undocumented) - event_label?: string; - // (undocumented) - fatal?: boolean; - firebase_screen?: string; - firebase_screen_class?: string; - // (undocumented) - item_id?: string; - // (undocumented) - item_list_id?: string; - // (undocumented) - item_list_name?: string; - // (undocumented) - items?: Item[]; - // (undocumented) - method?: string; - // (undocumented) - number?: string; - // (undocumented) - page_location?: string; - // (undocumented) - page_path?: string; - // (undocumented) - page_title?: string; - // (undocumented) - payment_type?: string; - // (undocumented) - promotion_id?: string; - // (undocumented) - promotion_name?: string; - // (undocumented) - promotions?: Promotion[]; - // (undocumented) - screen_name?: string; - // (undocumented) - search_term?: string; - // (undocumented) - shipping?: Currency; - // (undocumented) - shipping_tier?: string; - // (undocumented) - tax?: Currency; - // (undocumented) - transaction_id?: string; - // (undocumented) - value?: number; -} - -// @public -export function getAnalytics(app?: FirebaseApp): Analytics; - -// @public -export interface GtagConfigParams { - 'allow_google_signals?': boolean; - // (undocumented) - [key: string]: unknown; - 'allow_ad_personalization_signals'?: boolean; - 'cookie_domain'?: string; - 'cookie_expires'?: number; - 'cookie_flags'?: string; - 'cookie_prefix'?: string; - 'cookie_update'?: boolean; - 'page_location'?: string; - 'page_title'?: string; - 'send_page_view'?: boolean; -} - -// @public -export function initializeAnalytics(app: FirebaseApp, options?: AnalyticsSettings): Analytics; - -// @public -export function isSupported(): Promise; - -// @public -export interface Item { - // (undocumented) - affiliation?: string; - // @deprecated (undocumented) - brand?: string; - // @deprecated (undocumented) - category?: string; - // (undocumented) - coupon?: string; - // (undocumented) - creative_name?: string; - // (undocumented) - creative_slot?: string; - // (undocumented) - discount?: Currency; - // @deprecated (undocumented) - id?: string; - // (undocumented) - index?: number; - // (undocumented) - item_brand?: string; - // (undocumented) - item_category?: string; - // (undocumented) - item_category2?: string; - // (undocumented) - item_category3?: string; - // (undocumented) - item_category4?: string; - // (undocumented) - item_category5?: string; - // (undocumented) - item_id?: string; - // (undocumented) - item_list_id?: string; - // (undocumented) - item_list_name?: string; - // (undocumented) - item_name?: string; - // (undocumented) - item_variant?: string; - // (undocumented) - location_id?: string; - // @deprecated (undocumented) - name?: string; - // (undocumented) - price?: Currency; - // (undocumented) - promotion_id?: string; - // (undocumented) - promotion_name?: string; - // (undocumented) - quantity?: number; -} - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'add_payment_info', eventParams?: { - coupon?: EventParams['coupon']; - currency?: EventParams['currency']; - items?: EventParams['items']; - payment_type?: EventParams['payment_type']; - value?: EventParams['value']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'add_shipping_info', eventParams?: { - coupon?: EventParams['coupon']; - currency?: EventParams['currency']; - items?: EventParams['items']; - shipping_tier?: EventParams['shipping_tier']; - value?: EventParams['value']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'add_to_cart' | 'add_to_wishlist' | 'remove_from_cart', eventParams?: { - currency?: EventParams['currency']; - value?: EventParams['value']; - items?: EventParams['items']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'begin_checkout', eventParams?: { - currency?: EventParams['currency']; - coupon?: EventParams['coupon']; - value?: EventParams['value']; - items?: EventParams['items']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'checkout_progress', eventParams?: { - currency?: EventParams['currency']; - coupon?: EventParams['coupon']; - value?: EventParams['value']; - items?: EventParams['items']; - checkout_step?: EventParams['checkout_step']; - checkout_option?: EventParams['checkout_option']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'exception', eventParams?: { - description?: EventParams['description']; - fatal?: EventParams['fatal']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'generate_lead', eventParams?: { - value?: EventParams['value']; - currency?: EventParams['currency']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'login', eventParams?: { - method?: EventParams['method']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'page_view', eventParams?: { - page_title?: string; - page_location?: string; - page_path?: string; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'purchase' | 'refund', eventParams?: { - value?: EventParams['value']; - currency?: EventParams['currency']; - transaction_id: EventParams['transaction_id']; - tax?: EventParams['tax']; - shipping?: EventParams['shipping']; - items?: EventParams['items']; - coupon?: EventParams['coupon']; - affiliation?: EventParams['affiliation']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'screen_view', eventParams?: { - firebase_screen: EventParams['firebase_screen']; - firebase_screen_class: EventParams['firebase_screen_class']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'search' | 'view_search_results', eventParams?: { - search_term?: EventParams['search_term']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'select_content', eventParams?: { - content_type?: EventParams['content_type']; - item_id?: EventParams['item_id']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'select_item', eventParams?: { - items?: EventParams['items']; - item_list_name?: EventParams['item_list_name']; - item_list_id?: EventParams['item_list_id']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'select_promotion' | 'view_promotion', eventParams?: { - items?: EventParams['items']; - promotion_id?: EventParams['promotion_id']; - promotion_name?: EventParams['promotion_name']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'set_checkout_option', eventParams?: { - checkout_step?: EventParams['checkout_step']; - checkout_option?: EventParams['checkout_option']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'share', eventParams?: { - method?: EventParams['method']; - content_type?: EventParams['content_type']; - item_id?: EventParams['item_id']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'sign_up', eventParams?: { - method?: EventParams['method']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'timing_complete', eventParams?: { - name: string; - value: number; - event_category?: string; - event_label?: string; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'view_cart' | 'view_item', eventParams?: { - currency?: EventParams['currency']; - items?: EventParams['items']; - value?: EventParams['value']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: 'view_item_list', eventParams?: { - items?: EventParams['items']; - item_list_name?: EventParams['item_list_name']; - item_list_id?: EventParams['item_list_id']; - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public -export function logEvent(analyticsInstance: Analytics, eventName: CustomEventName, eventParams?: { - [key: string]: any; -}, options?: AnalyticsCallOptions): void; - -// @public @deprecated -export interface Promotion { - // (undocumented) - creative_name?: string; - // (undocumented) - creative_slot?: string; - // (undocumented) - id?: string; - // (undocumented) - name?: string; -} - -// @public -export function setAnalyticsCollectionEnabled(analyticsInstance: Analytics, enabled: boolean): void; - -// @public -export function setCurrentScreen(analyticsInstance: Analytics, screenName: string, options?: AnalyticsCallOptions): void; - -// @public -export function settings(options: SettingsOptions): void; - -// @public -export interface SettingsOptions { - dataLayerName?: string; - gtagName?: string; -} - -// @public -export function setUserId(analyticsInstance: Analytics, id: string, options?: AnalyticsCallOptions): void; - -// @public -export function setUserProperties(analyticsInstance: Analytics, properties: CustomParams, options?: AnalyticsCallOptions): void; - - -``` +## API Report File for "@firebase/analytics" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; + +// @public +export interface Analytics { + app: FirebaseApp; +} + +// @public +export interface AnalyticsCallOptions { + global: boolean; +} + +// @public +export interface AnalyticsSettings { + config?: GtagConfigParams | EventParams; +} + +// @public +export interface ControlParams { + // (undocumented) + event_callback?: () => void; + // (undocumented) + event_timeout?: number; + // (undocumented) + groups?: string | string[]; + // (undocumented) + send_to?: string | string[]; +} + +// @public +export type Currency = string | number; + +// @public +export type CustomEventName = T extends EventNameString ? never : T; + +// @public +export interface CustomParams { + // (undocumented) + [key: string]: unknown; +} + +// @public +export type EventNameString = 'add_payment_info' | 'add_shipping_info' | 'add_to_cart' | 'add_to_wishlist' | 'begin_checkout' | 'checkout_progress' | 'exception' | 'generate_lead' | 'login' | 'page_view' | 'purchase' | 'refund' | 'remove_from_cart' | 'screen_view' | 'search' | 'select_content' | 'select_item' | 'select_promotion' | 'set_checkout_option' | 'share' | 'sign_up' | 'timing_complete' | 'view_cart' | 'view_item' | 'view_item_list' | 'view_promotion' | 'view_search_results'; + +// @public +export interface EventParams { + // (undocumented) + [key: string]: unknown; + // (undocumented) + affiliation?: string; + // (undocumented) + checkout_option?: string; + // (undocumented) + checkout_step?: number; + // (undocumented) + content_type?: string; + // (undocumented) + coupon?: string; + // (undocumented) + currency?: string; + // (undocumented) + description?: string; + // (undocumented) + event_category?: string; + // (undocumented) + event_label?: string; + // (undocumented) + fatal?: boolean; + firebase_screen?: string; + firebase_screen_class?: string; + // (undocumented) + item_id?: string; + // (undocumented) + item_list_id?: string; + // (undocumented) + item_list_name?: string; + // (undocumented) + items?: Item[]; + // (undocumented) + method?: string; + // (undocumented) + number?: string; + // (undocumented) + page_location?: string; + // (undocumented) + page_path?: string; + // (undocumented) + page_title?: string; + // (undocumented) + payment_type?: string; + // (undocumented) + promotion_id?: string; + // (undocumented) + promotion_name?: string; + // (undocumented) + promotions?: Promotion[]; + // (undocumented) + screen_name?: string; + // (undocumented) + search_term?: string; + // (undocumented) + shipping?: Currency; + // (undocumented) + shipping_tier?: string; + // (undocumented) + tax?: Currency; + // (undocumented) + transaction_id?: string; + // (undocumented) + value?: number; +} + +// @public +export function getAnalytics(app?: FirebaseApp): Analytics; + +// @public +export interface GtagConfigParams { + 'allow_google_signals?': boolean; + // (undocumented) + [key: string]: unknown; + 'allow_ad_personalization_signals'?: boolean; + 'cookie_domain'?: string; + 'cookie_expires'?: number; + 'cookie_flags'?: string; + 'cookie_prefix'?: string; + 'cookie_update'?: boolean; + 'page_location'?: string; + 'page_title'?: string; + 'send_page_view'?: boolean; +} + +// @public +export function initializeAnalytics(app: FirebaseApp, options?: AnalyticsSettings): Analytics; + +// @public +export function isSupported(): Promise; + +// @public +export interface Item { + // (undocumented) + affiliation?: string; + // @deprecated (undocumented) + brand?: string; + // @deprecated (undocumented) + category?: string; + // (undocumented) + coupon?: string; + // (undocumented) + creative_name?: string; + // (undocumented) + creative_slot?: string; + // (undocumented) + discount?: Currency; + // @deprecated (undocumented) + id?: string; + // (undocumented) + index?: number; + // (undocumented) + item_brand?: string; + // (undocumented) + item_category?: string; + // (undocumented) + item_category2?: string; + // (undocumented) + item_category3?: string; + // (undocumented) + item_category4?: string; + // (undocumented) + item_category5?: string; + // (undocumented) + item_id?: string; + // (undocumented) + item_list_id?: string; + // (undocumented) + item_list_name?: string; + // (undocumented) + item_name?: string; + // (undocumented) + item_variant?: string; + // (undocumented) + location_id?: string; + // @deprecated (undocumented) + name?: string; + // (undocumented) + price?: Currency; + // (undocumented) + promotion_id?: string; + // (undocumented) + promotion_name?: string; + // (undocumented) + quantity?: number; +} + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'add_payment_info', eventParams?: { + coupon?: EventParams['coupon']; + currency?: EventParams['currency']; + items?: EventParams['items']; + payment_type?: EventParams['payment_type']; + value?: EventParams['value']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'add_shipping_info', eventParams?: { + coupon?: EventParams['coupon']; + currency?: EventParams['currency']; + items?: EventParams['items']; + shipping_tier?: EventParams['shipping_tier']; + value?: EventParams['value']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'add_to_cart' | 'add_to_wishlist' | 'remove_from_cart', eventParams?: { + currency?: EventParams['currency']; + value?: EventParams['value']; + items?: EventParams['items']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'begin_checkout', eventParams?: { + currency?: EventParams['currency']; + coupon?: EventParams['coupon']; + value?: EventParams['value']; + items?: EventParams['items']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'checkout_progress', eventParams?: { + currency?: EventParams['currency']; + coupon?: EventParams['coupon']; + value?: EventParams['value']; + items?: EventParams['items']; + checkout_step?: EventParams['checkout_step']; + checkout_option?: EventParams['checkout_option']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'exception', eventParams?: { + description?: EventParams['description']; + fatal?: EventParams['fatal']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'generate_lead', eventParams?: { + value?: EventParams['value']; + currency?: EventParams['currency']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'login', eventParams?: { + method?: EventParams['method']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'page_view', eventParams?: { + page_title?: string; + page_location?: string; + page_path?: string; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'purchase' | 'refund', eventParams?: { + value?: EventParams['value']; + currency?: EventParams['currency']; + transaction_id: EventParams['transaction_id']; + tax?: EventParams['tax']; + shipping?: EventParams['shipping']; + items?: EventParams['items']; + coupon?: EventParams['coupon']; + affiliation?: EventParams['affiliation']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'screen_view', eventParams?: { + firebase_screen: EventParams['firebase_screen']; + firebase_screen_class: EventParams['firebase_screen_class']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'search' | 'view_search_results', eventParams?: { + search_term?: EventParams['search_term']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'select_content', eventParams?: { + content_type?: EventParams['content_type']; + item_id?: EventParams['item_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'select_item', eventParams?: { + items?: EventParams['items']; + item_list_name?: EventParams['item_list_name']; + item_list_id?: EventParams['item_list_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'select_promotion' | 'view_promotion', eventParams?: { + items?: EventParams['items']; + promotion_id?: EventParams['promotion_id']; + promotion_name?: EventParams['promotion_name']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'set_checkout_option', eventParams?: { + checkout_step?: EventParams['checkout_step']; + checkout_option?: EventParams['checkout_option']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'share', eventParams?: { + method?: EventParams['method']; + content_type?: EventParams['content_type']; + item_id?: EventParams['item_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'sign_up', eventParams?: { + method?: EventParams['method']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'timing_complete', eventParams?: { + name: string; + value: number; + event_category?: string; + event_label?: string; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'view_cart' | 'view_item', eventParams?: { + currency?: EventParams['currency']; + items?: EventParams['items']; + value?: EventParams['value']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'view_item_list', eventParams?: { + items?: EventParams['items']; + item_list_name?: EventParams['item_list_name']; + item_list_id?: EventParams['item_list_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: CustomEventName, eventParams?: { + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public @deprecated +export interface Promotion { + // (undocumented) + creative_name?: string; + // (undocumented) + creative_slot?: string; + // (undocumented) + id?: string; + // (undocumented) + name?: string; +} + +// @public +export function setAnalyticsCollectionEnabled(analyticsInstance: Analytics, enabled: boolean): void; + +// @public +export function setCurrentScreen(analyticsInstance: Analytics, screenName: string, options?: AnalyticsCallOptions): void; + +// @public +export function settings(options: SettingsOptions): void; + +// @public +export interface SettingsOptions { + dataLayerName?: string; + gtagName?: string; +} + +// @public +export function setUserId(analyticsInstance: Analytics, id: string, options?: AnalyticsCallOptions): void; + +// @public +export function setUserProperties(analyticsInstance: Analytics, properties: CustomParams, options?: AnalyticsCallOptions): void; + + +``` diff --git a/common/api-review/app-check-exp.api.md b/common/api-review/app-check.api.md similarity index 91% rename from common/api-review/app-check-exp.api.md rename to common/api-review/app-check.api.md index 976284c1cad..154e7ce2295 100644 --- a/common/api-review/app-check-exp.api.md +++ b/common/api-review/app-check.api.md @@ -1,94 +1,94 @@ -## API Report File for "@firebase/app-check-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { FirebaseApp } from '@firebase/app-exp'; -import { PartialObserver } from '@firebase/util'; -import { Unsubscribe } from '@firebase/util'; - -// @public -export interface AppCheck { - app: FirebaseApp; -} - -// @internal (undocumented) -export type _AppCheckComponentName = 'app-check-exp'; - -// @internal (undocumented) -export type _AppCheckInternalComponentName = 'app-check-internal'; - -// @public -export interface AppCheckOptions { - isTokenAutoRefreshEnabled?: boolean; - provider: CustomProvider | ReCaptchaV3Provider; -} - -// @public -export interface AppCheckToken { - readonly expireTimeMillis: number; - // (undocumented) - readonly token: string; -} - -// @public -export type AppCheckTokenListener = (token: AppCheckTokenResult) => void; - -// @public -export interface AppCheckTokenResult { - readonly token: string; -} - -// Warning: (ae-forgotten-export) The symbol "AppCheckProvider" needs to be exported by the entry point index.d.ts -// -// @public -export class CustomProvider implements AppCheckProvider { - constructor(_customProviderOptions: CustomProviderOptions); - // Warning: (ae-forgotten-export) The symbol "AppCheckTokenInternal" needs to be exported by the entry point index.d.ts - // - // @internal (undocumented) - getToken(): Promise; - // @internal (undocumented) - initialize(app: FirebaseApp): void; - // @internal (undocumented) - isEqual(otherProvider: unknown): boolean; -} - -// @public -export interface CustomProviderOptions { - getToken: () => Promise; -} - -// @public -export function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise; - -// @public -export function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck; - -// @public -export function onTokenChanged(appCheckInstance: AppCheck, observer: PartialObserver): Unsubscribe; - -// @public -export function onTokenChanged(appCheckInstance: AppCheck, onNext: (tokenResult: AppCheckTokenResult) => void, onError?: (error: Error) => void, onCompletion?: () => void): Unsubscribe; - -export { PartialObserver } - -// @public -export class ReCaptchaV3Provider implements AppCheckProvider { - constructor(_siteKey: string); - // @internal - getToken(): Promise; - // @internal (undocumented) - initialize(app: FirebaseApp): void; - // @internal (undocumented) - isEqual(otherProvider: unknown): boolean; - } - -// @public -export function setTokenAutoRefreshEnabled(appCheckInstance: AppCheck, isTokenAutoRefreshEnabled: boolean): void; - -export { Unsubscribe } - - -``` +## API Report File for "@firebase/app-check" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; +import { PartialObserver } from '@firebase/util'; +import { Unsubscribe } from '@firebase/util'; + +// @public +export interface AppCheck { + app: FirebaseApp; +} + +// @internal (undocumented) +export type _AppCheckComponentName = 'app-check'; + +// @internal (undocumented) +export type _AppCheckInternalComponentName = 'app-check-internal'; + +// @public +export interface AppCheckOptions { + isTokenAutoRefreshEnabled?: boolean; + provider: CustomProvider | ReCaptchaV3Provider; +} + +// @public +export interface AppCheckToken { + readonly expireTimeMillis: number; + // (undocumented) + readonly token: string; +} + +// @public +export type AppCheckTokenListener = (token: AppCheckTokenResult) => void; + +// @public +export interface AppCheckTokenResult { + readonly token: string; +} + +// Warning: (ae-forgotten-export) The symbol "AppCheckProvider" needs to be exported by the entry point index.d.ts +// +// @public +export class CustomProvider implements AppCheckProvider { + constructor(_customProviderOptions: CustomProviderOptions); + // Warning: (ae-forgotten-export) The symbol "AppCheckTokenInternal" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + getToken(): Promise; + // @internal (undocumented) + initialize(app: FirebaseApp): void; + // @internal (undocumented) + isEqual(otherProvider: unknown): boolean; +} + +// @public +export interface CustomProviderOptions { + getToken: () => Promise; +} + +// @public +export function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise; + +// @public +export function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck; + +// @public +export function onTokenChanged(appCheckInstance: AppCheck, observer: PartialObserver): Unsubscribe; + +// @public +export function onTokenChanged(appCheckInstance: AppCheck, onNext: (tokenResult: AppCheckTokenResult) => void, onError?: (error: Error) => void, onCompletion?: () => void): Unsubscribe; + +export { PartialObserver } + +// @public +export class ReCaptchaV3Provider implements AppCheckProvider { + constructor(_siteKey: string); + // @internal + getToken(): Promise; + // @internal (undocumented) + initialize(app: FirebaseApp): void; + // @internal (undocumented) + isEqual(otherProvider: unknown): boolean; + } + +// @public +export function setTokenAutoRefreshEnabled(appCheckInstance: AppCheck, isTokenAutoRefreshEnabled: boolean): void; + +export { Unsubscribe } + + +``` diff --git a/common/api-review/app-exp.api.md b/common/api-review/app.api.md similarity index 95% rename from common/api-review/app-exp.api.md rename to common/api-review/app.api.md index 75f20fb2612..8d35bc4096f 100644 --- a/common/api-review/app-exp.api.md +++ b/common/api-review/app.api.md @@ -1,112 +1,115 @@ -## API Report File for "@firebase/app-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { Component } from '@firebase/component'; -import { ComponentContainer } from '@firebase/component'; -import { LogCallback } from '@firebase/logger'; -import { LogLevelString } from '@firebase/logger'; -import { LogOptions } from '@firebase/logger'; -import { Name } from '@firebase/component'; -import { Provider } from '@firebase/component'; - -// @internal (undocumented) -export function _addComponent(app: FirebaseApp, component: Component): void; - -// @internal (undocumented) -export function _addOrOverwriteComponent(app: FirebaseApp, component: Component): void; - -// @internal (undocumented) -export const _apps: Map; - -// @internal -export function _clearComponents(): void; - -// @internal -export const _components: Map>; - -// @internal -export const _DEFAULT_ENTRY_NAME = "[DEFAULT]"; - -// @public -export function deleteApp(app: FirebaseApp): Promise; - -// @public -export interface FirebaseApp { - automaticDataCollectionEnabled: boolean; - readonly name: string; - readonly options: FirebaseOptions; -} - -// @internal (undocumented) -export interface _FirebaseAppInternal extends FirebaseApp { - // (undocumented) - checkDestroyed(): void; - // (undocumented) - container: ComponentContainer; - // (undocumented) - isDeleted: boolean; -} - -// @public -export interface FirebaseAppSettings { - automaticDataCollectionEnabled?: boolean; - name?: string; -} - -// @public -export interface FirebaseOptions { - apiKey?: string; - appId?: string; - authDomain?: string; - databaseURL?: string; - measurementId?: string; - messagingSenderId?: string; - projectId?: string; - storageBucket?: string; -} - -// @internal (undocumented) -export interface _FirebaseService { - // (undocumented) - app: FirebaseApp; - _delete(): Promise; -} - -// @public -export function getApp(name?: string): FirebaseApp; - -// @public -export function getApps(): FirebaseApp[]; - -// @internal (undocumented) -export function _getProvider(app: FirebaseApp, name: T): Provider; - -// @public -export function initializeApp(options: FirebaseOptions, name?: string): FirebaseApp; - -// @public -export function initializeApp(options: FirebaseOptions, config?: FirebaseAppSettings): FirebaseApp; - -// @public -export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; - -// @internal (undocumented) -export function _registerComponent(component: Component): boolean; - -// @public -export function registerVersion(libraryKeyOrName: string, version: string, variant?: string): void; - -// @internal (undocumented) -export function _removeServiceInstance(app: FirebaseApp, name: T, instanceIdentifier?: string): void; - -// @public -export const SDK_VERSION: string; - -// @public -export function setLogLevel(logLevel: LogLevelString): void; - - -``` +## API Report File for "@firebase/app" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Component } from '@firebase/component'; +import { ComponentContainer } from '@firebase/component'; +import { FirebaseError } from '@firebase/util'; +import { LogCallback } from '@firebase/logger'; +import { LogLevelString } from '@firebase/logger'; +import { LogOptions } from '@firebase/logger'; +import { Name } from '@firebase/component'; +import { Provider } from '@firebase/component'; + +// @internal (undocumented) +export function _addComponent(app: FirebaseApp, component: Component): void; + +// @internal (undocumented) +export function _addOrOverwriteComponent(app: FirebaseApp, component: Component): void; + +// @internal (undocumented) +export const _apps: Map; + +// @internal +export function _clearComponents(): void; + +// @internal +export const _components: Map>; + +// @internal +export const _DEFAULT_ENTRY_NAME = "[DEFAULT]"; + +// @public +export function deleteApp(app: FirebaseApp): Promise; + +// @public +export interface FirebaseApp { + automaticDataCollectionEnabled: boolean; + readonly name: string; + readonly options: FirebaseOptions; +} + +// @internal (undocumented) +export interface _FirebaseAppInternal extends FirebaseApp { + // (undocumented) + checkDestroyed(): void; + // (undocumented) + container: ComponentContainer; + // (undocumented) + isDeleted: boolean; +} + +// @public +export interface FirebaseAppSettings { + automaticDataCollectionEnabled?: boolean; + name?: string; +} + +export { FirebaseError } + +// @public +export interface FirebaseOptions { + apiKey?: string; + appId?: string; + authDomain?: string; + databaseURL?: string; + measurementId?: string; + messagingSenderId?: string; + projectId?: string; + storageBucket?: string; +} + +// @internal (undocumented) +export interface _FirebaseService { + // (undocumented) + app: FirebaseApp; + _delete(): Promise; +} + +// @public +export function getApp(name?: string): FirebaseApp; + +// @public +export function getApps(): FirebaseApp[]; + +// @internal (undocumented) +export function _getProvider(app: FirebaseApp, name: T): Provider; + +// @public +export function initializeApp(options: FirebaseOptions, name?: string): FirebaseApp; + +// @public +export function initializeApp(options: FirebaseOptions, config?: FirebaseAppSettings): FirebaseApp; + +// @public +export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; + +// @internal (undocumented) +export function _registerComponent(component: Component): boolean; + +// @public +export function registerVersion(libraryKeyOrName: string, version: string, variant?: string): void; + +// @internal (undocumented) +export function _removeServiceInstance(app: FirebaseApp, name: T, instanceIdentifier?: string): void; + +// @public +export const SDK_VERSION: string; + +// @public +export function setLogLevel(logLevel: LogLevelString): void; + + +``` diff --git a/common/api-review/auth-exp.api.md b/common/api-review/auth.api.md similarity index 94% rename from common/api-review/auth-exp.api.md rename to common/api-review/auth.api.md index dd101d24fb7..f0532091bbe 100644 --- a/common/api-review/auth-exp.api.md +++ b/common/api-review/auth.api.md @@ -1,4 +1,4 @@ -## API Report File for "@firebase/auth-exp" +## API Report File for "@firebase/auth" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). @@ -7,7 +7,7 @@ import { CompleteFn } from '@firebase/util'; import { ErrorFactory } from '@firebase/util'; import { ErrorFn } from '@firebase/util'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { FirebaseError } from '@firebase/util'; import { NextFn } from '@firebase/util'; import { Observer } from '@firebase/util'; @@ -80,6 +80,7 @@ export function applyActionCode(auth: Auth, oobCode: string): Promise; // @public export interface Auth { + readonly app: FirebaseApp; readonly config: Config; readonly currentUser: User | null; readonly emulatorConfig: EmulatorConfig | null; @@ -101,14 +102,14 @@ export class AuthCredential { protected constructor( providerId: string, signInMethod: string); - // Warning: (ae-forgotten-export) The symbol "AuthInternal" needs to be exported by the entry point index.doc.d.ts - // Warning: (ae-forgotten-export) The symbol "PhoneOrOauthTokenResponse" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "AuthInternal" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "PhoneOrOauthTokenResponse" needs to be exported by the entry point index.d.ts // // @internal (undocumented) _getIdTokenResponse(_auth: AuthInternal): Promise; // @internal (undocumented) _getReauthenticationResolver(_auth: AuthInternal): Promise; - // Warning: (ae-forgotten-export) The symbol "IdTokenResponse" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "IdTokenResponse" needs to be exported by the entry point index.d.ts // // @internal (undocumented) _linkToIdToken(_auth: AuthInternal, _idToken: string): Promise; @@ -276,9 +277,6 @@ export function connectAuthEmulator(auth: Auth, url: string, options?: { disableWarnings: boolean; }): void; -// @public -export const cordovaPopupRedirectResolver: PopupRedirectResolver; - // @public export function createUserWithEmailAndPassword(auth: Auth, email: string, password: string): Promise; @@ -342,7 +340,7 @@ export interface EmulatorConfig { export { ErrorFn } -// Warning: (ae-forgotten-export) The symbol "BaseOAuthProvider" needs to be exported by the entry point index.doc.d.ts +// Warning: (ae-forgotten-export) The symbol "BaseOAuthProvider" needs to be exported by the entry point index.d.ts // // @public export class FacebookAuthProvider extends BaseOAuthProvider { @@ -484,7 +482,7 @@ export type NextOrObserver = NextFn | Observer; export class OAuthCredential extends AuthCredential { accessToken?: string; static fromJSON(json: string | object): OAuthCredential | null; - // Warning: (ae-forgotten-export) The symbol "OAuthCredentialParams" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "OAuthCredentialParams" needs to be exported by the entry point index.d.ts // // @internal (undocumented) static _fromParams(params: OAuthCredentialParams): OAuthCredential; @@ -563,7 +561,7 @@ export class PhoneAuthCredential extends AuthCredential { _getReauthenticationResolver(auth: AuthInternal): Promise; // @internal (undocumented) _linkToIdToken(auth: AuthInternal, idToken: string): Promise; - // Warning: (ae-forgotten-export) The symbol "SignInWithPhoneNumberRequest" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "SignInWithPhoneNumberRequest" needs to be exported by the entry point index.d.ts // // @internal (undocumented) _makeVerificationRequest(): SignInWithPhoneNumberRequest; @@ -636,9 +634,6 @@ export interface ReactNativeAsyncStorage { setItem(key: string, value: string): Promise; } -// @public -export const reactNativeLocalPersistence: Persistence; - // @public export function reauthenticateWithCredential(user: User, credential: AuthCredential): Promise; @@ -657,13 +652,13 @@ export interface RecaptchaParameters { [key: string]: any; } -// Warning: (ae-forgotten-export) The symbol "ApplicationVerifierInternal" needs to be exported by the entry point index.doc.d.ts +// Warning: (ae-forgotten-export) The symbol "ApplicationVerifierInternal" needs to be exported by the entry point index.d.ts // // @public export class RecaptchaVerifier implements ApplicationVerifierInternal { constructor(containerOrId: HTMLElement | string, parameters: RecaptchaParameters, authExtern: Auth); clear(): void; - // Warning: (ae-forgotten-export) The symbol "ReCaptchaLoader" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "ReCaptchaLoader" needs to be exported by the entry point index.d.ts // // @internal (undocumented) readonly _recaptchaLoader: ReCaptchaLoader; @@ -677,7 +672,7 @@ export class RecaptchaVerifier implements ApplicationVerifierInternal { // @public export function reload(user: User): Promise; -// Warning: (ae-forgotten-export) The symbol "FederatedAuthProvider" needs to be exported by the entry point index.doc.d.ts +// Warning: (ae-forgotten-export) The symbol "FederatedAuthProvider" needs to be exported by the entry point index.d.ts // // @public export class SAMLAuthProvider extends FederatedAuthProvider { @@ -821,6 +816,4 @@ export function verifyBeforeUpdateEmail(user: User, newEmail: string, actionCode export function verifyPasswordResetCode(auth: Auth, code: string): Promise; -// (No @packageDocumentation comment for this package) - ``` diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 0d3d3c0ea18..70f9a27dba6 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -5,7 +5,7 @@ ```ts import { EmulatorMockTokenOptions } from '@firebase/util'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { LogLevelString as LogLevel } from '@firebase/logger'; // @public @@ -104,6 +104,8 @@ export class DocumentSnapshot { get ref(): DocumentReference; } +export { EmulatorMockTokenOptions } + // @public export function endAt(snapshot: DocumentSnapshot): QueryConstraint; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 8d9ba3034ae..56ee1729a4e 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -1,520 +1,522 @@ -## API Report File for "@firebase/firestore" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { EmulatorMockTokenOptions } from '@firebase/util'; -import { FirebaseApp } from '@firebase/app-exp'; -import { LogLevelString as LogLevel } from '@firebase/logger'; - -// @public -export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; - -// @public -export type AddPrefixToKeys> = { - [K in keyof T & string as `${Prefix}.${K}`]+?: T[K]; -}; - -// @public -export function arrayRemove(...elements: unknown[]): FieldValue; - -// @public -export function arrayUnion(...elements: unknown[]): FieldValue; - -// @public -export class Bytes { - static fromBase64String(base64: string): Bytes; - static fromUint8Array(array: Uint8Array): Bytes; - isEqual(other: Bytes): boolean; - toBase64(): string; - toString(): string; - toUint8Array(): Uint8Array; -} - -// @public -export const CACHE_SIZE_UNLIMITED = -1; - -// @public -export function clearIndexedDbPersistence(firestore: Firestore): Promise; - -// @public -export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collection(reference: CollectionReference, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collection(reference: DocumentReference, path: string, ...pathSegments: string[]): CollectionReference; - -// @public -export function collectionGroup(firestore: Firestore, collectionId: string): Query; - -// @public -export class CollectionReference extends Query { - get id(): string; - get parent(): DocumentReference | null; - get path(): string; - readonly type = "collection"; - withConverter(converter: FirestoreDataConverter): CollectionReference; - withConverter(converter: null): CollectionReference; -} - -// @public -export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: { - mockUserToken?: EmulatorMockTokenOptions | string; -}): void; - -// @public -export function deleteDoc(reference: DocumentReference): Promise; - -// @public -export function deleteField(): FieldValue; - -// @public -export function disableNetwork(firestore: Firestore): Promise; - -// @public -export function doc(firestore: Firestore, path: string, ...pathSegments: string[]): DocumentReference; - -// @public -export function doc(reference: CollectionReference, path?: string, ...pathSegments: string[]): DocumentReference; - -// @public -export function doc(reference: DocumentReference, path: string, ...pathSegments: string[]): DocumentReference; - -// @public -export interface DocumentChange { - readonly doc: QueryDocumentSnapshot; - readonly newIndex: number; - readonly oldIndex: number; - readonly type: DocumentChangeType; -} - -// @public -export type DocumentChangeType = 'added' | 'removed' | 'modified'; - -// @public -export interface DocumentData { - [field: string]: any; -} - -// @public -export function documentId(): FieldPath; - -// @public -export class DocumentReference { - readonly converter: FirestoreDataConverter | null; - readonly firestore: Firestore; - get id(): string; - get parent(): CollectionReference; - get path(): string; - readonly type = "document"; - withConverter(converter: FirestoreDataConverter): DocumentReference; - withConverter(converter: null): DocumentReference; -} - -// @public -export class DocumentSnapshot { - protected constructor(); - data(options?: SnapshotOptions): T | undefined; - exists(): this is QueryDocumentSnapshot; - get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; - get id(): string; - readonly metadata: SnapshotMetadata; - get ref(): DocumentReference; -} - -// @public -export function enableIndexedDbPersistence(firestore: Firestore, persistenceSettings?: PersistenceSettings): Promise; - -// @public -export function enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise; - -// @public -export function enableNetwork(firestore: Firestore): Promise; - -// @public -export function endAt(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function endAt(...fieldValues: unknown[]): QueryConstraint; - -// @public -export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function endBefore(...fieldValues: unknown[]): QueryConstraint; - -// @public -export class FieldPath { - constructor(...fieldNames: string[]); - isEqual(other: FieldPath): boolean; -} - -// @public -export abstract class FieldValue { - abstract isEqual(other: FieldValue): boolean; -} - -// @public -export class Firestore { - get app(): FirebaseApp; - toJSON(): object; - type: 'firestore-lite' | 'firestore'; -} - -// @public -export interface FirestoreDataConverter { - fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): T; - toFirestore(modelObject: WithFieldValue): DocumentData; - toFirestore(modelObject: PartialWithFieldValue, options: SetOptions): DocumentData; -} - -// @public -export class FirestoreError extends Error { - readonly code: FirestoreErrorCode; - readonly message: string; - readonly name: string; - readonly stack?: string; -} - -// @public -export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; - -// @public -export interface FirestoreSettings { - cacheSizeBytes?: number; - experimentalAutoDetectLongPolling?: boolean; - experimentalForceLongPolling?: boolean; - host?: string; - ignoreUndefinedProperties?: boolean; - ssl?: boolean; -} - -// @public -export class GeoPoint { - constructor(latitude: number, longitude: number); - isEqual(other: GeoPoint): boolean; - get latitude(): number; - get longitude(): number; - toJSON(): { - latitude: number; - longitude: number; - }; -} - -// @public -export function getDoc(reference: DocumentReference): Promise>; - -// @public -export function getDocFromCache(reference: DocumentReference): Promise>; - -// @public -export function getDocFromServer(reference: DocumentReference): Promise>; - -// @public -export function getDocs(query: Query): Promise>; - -// @public -export function getDocsFromCache(query: Query): Promise>; - -// @public -export function getDocsFromServer(query: Query): Promise>; - -// @public -export function getFirestore(app?: FirebaseApp): Firestore; - -// @public -export function increment(n: number): FieldValue; - -// @public -export function initializeFirestore(app: FirebaseApp, settings: FirestoreSettings): Firestore; - -// @public -export function limit(limit: number): QueryConstraint; - -// @public -export function limitToLast(limit: number): QueryConstraint; - -// @public -export function loadBundle(firestore: Firestore, bundleData: ReadableStream | ArrayBuffer | string): LoadBundleTask; - -// @public -export class LoadBundleTask implements PromiseLike { - catch(onRejected: (a: Error) => R | PromiseLike): Promise; - onProgress(next?: (progress: LoadBundleTaskProgress) => unknown, error?: (err: Error) => unknown, complete?: () => void): void; - then(onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, onRejected?: (a: Error) => R | PromiseLike): Promise; -} - -// @public -export interface LoadBundleTaskProgress { - bytesLoaded: number; - documentsLoaded: number; - taskState: TaskState; - totalBytes: number; - totalDocuments: number; -} - -export { LogLevel } - -// @public -export function namedQuery(firestore: Firestore, name: string): Promise; - -// @public -export type NestedUpdateFields> = UnionToIntersection<{ - [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; -}[keyof T & string]>; - -// @public -export function onSnapshot(reference: DocumentReference, observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(query: Query, observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(query: Query, options: SnapshotListenOptions, observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshot(query: Query, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; - -// @public -export function onSnapshotsInSync(firestore: Firestore, observer: { - next?: (value: void) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; -}): Unsubscribe; - -// @public -export function onSnapshotsInSync(firestore: Firestore, onSync: () => void): Unsubscribe; - -// @public -export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; - -// @public -export type OrderByDirection = 'desc' | 'asc'; - -// @public -export type PartialWithFieldValue = T extends Primitive ? T : T extends {} ? { - [K in keyof T]?: PartialWithFieldValue | FieldValue; -} : Partial; - -// @public -export interface PersistenceSettings { - forceOwnership?: boolean; -} - -// @public -export type Primitive = string | number | boolean | undefined | null; - -// @public -export class Query { - protected constructor(); - readonly converter: FirestoreDataConverter | null; - readonly firestore: Firestore; - readonly type: 'query' | 'collection'; - withConverter(converter: null): Query; - withConverter(converter: FirestoreDataConverter): Query; -} - -// @public -export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; - -// @public -export abstract class QueryConstraint { - abstract readonly type: QueryConstraintType; -} - -// @public -export type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; - -// @public -export class QueryDocumentSnapshot extends DocumentSnapshot { - // @override - data(options?: SnapshotOptions): T; -} - -// @public -export function queryEqual(left: Query, right: Query): boolean; - -// @public -export class QuerySnapshot { - docChanges(options?: SnapshotListenOptions): Array>; - get docs(): Array>; - get empty(): boolean; - forEach(callback: (result: QueryDocumentSnapshot) => void, thisArg?: unknown): void; - readonly metadata: SnapshotMetadata; - readonly query: Query; - get size(): number; -} - -// @public -export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; - -// @public -export function runTransaction(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise): Promise; - -// @public -export function serverTimestamp(): FieldValue; - -// @public -export function setDoc(reference: DocumentReference, data: WithFieldValue): Promise; - -// @public -export function setDoc(reference: DocumentReference, data: PartialWithFieldValue, options: SetOptions): Promise; - -// @public -export function setLogLevel(logLevel: LogLevel): void; - -// @public -export type SetOptions = { - readonly merge?: boolean; -} | { - readonly mergeFields?: Array; -}; - -// @public -export function snapshotEqual(left: DocumentSnapshot | QuerySnapshot, right: DocumentSnapshot | QuerySnapshot): boolean; - -// @public -export interface SnapshotListenOptions { - readonly includeMetadataChanges?: boolean; -} - -// @public -export class SnapshotMetadata { - readonly fromCache: boolean; - readonly hasPendingWrites: boolean; - isEqual(other: SnapshotMetadata): boolean; -} - -// @public -export interface SnapshotOptions { - readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; -} - -// @public -export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function startAfter(...fieldValues: unknown[]): QueryConstraint; - -// @public -export function startAt(snapshot: DocumentSnapshot): QueryConstraint; - -// @public -export function startAt(...fieldValues: unknown[]): QueryConstraint; - -// @public -export type TaskState = 'Error' | 'Running' | 'Success'; - -// @public -export function terminate(firestore: Firestore): Promise; - -// @public -export class Timestamp { - constructor( - seconds: number, - nanoseconds: number); - static fromDate(date: Date): Timestamp; - static fromMillis(milliseconds: number): Timestamp; - isEqual(other: Timestamp): boolean; - readonly nanoseconds: number; - static now(): Timestamp; - readonly seconds: number; - toDate(): Date; - toJSON(): { - seconds: number; - nanoseconds: number; - }; - toMillis(): number; - toString(): string; - valueOf(): string; -} - -// @public -export class Transaction { - delete(documentRef: DocumentReference): this; - get(documentRef: DocumentReference): Promise>; - set(documentRef: DocumentReference, data: WithFieldValue): this; - set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): this; - update(documentRef: DocumentReference, data: UpdateData): this; - update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; -} - -// @public -export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; - -// @public -export interface Unsubscribe { - (): void; -} - -// @public -export type UpdateData = T extends Primitive ? T : T extends Map ? Map, UpdateData> : T extends {} ? { - [K in keyof T]?: UpdateData | FieldValue; -} & NestedUpdateFields : Partial; - -// @public -export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; - -// @public -export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; - -// @public -export function waitForPendingWrites(firestore: Firestore): Promise; - -// @public -export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryConstraint; - -// @public -export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; - -// @public -export type WithFieldValue = T extends Primitive ? T : T extends {} ? { - [K in keyof T]: WithFieldValue | FieldValue; -} : Partial; - -// @public -export class WriteBatch { - commit(): Promise; - delete(documentRef: DocumentReference): WriteBatch; - set(documentRef: DocumentReference, data: WithFieldValue): WriteBatch; - set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): WriteBatch; - update(documentRef: DocumentReference, data: UpdateData): WriteBatch; - update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; -} - -// @public -export function writeBatch(firestore: Firestore): WriteBatch; - - -``` +## API Report File for "@firebase/firestore" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { EmulatorMockTokenOptions } from '@firebase/util'; +import { FirebaseApp } from '@firebase/app'; +import { LogLevelString as LogLevel } from '@firebase/logger'; + +// @public +export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; + +// @public +export type AddPrefixToKeys> = { + [K in keyof T & string as `${Prefix}.${K}`]+?: T[K]; +}; + +// @public +export function arrayRemove(...elements: unknown[]): FieldValue; + +// @public +export function arrayUnion(...elements: unknown[]): FieldValue; + +// @public +export class Bytes { + static fromBase64String(base64: string): Bytes; + static fromUint8Array(array: Uint8Array): Bytes; + isEqual(other: Bytes): boolean; + toBase64(): string; + toString(): string; + toUint8Array(): Uint8Array; +} + +// @public +export const CACHE_SIZE_UNLIMITED = -1; + +// @public +export function clearIndexedDbPersistence(firestore: Firestore): Promise; + +// @public +export function collection(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: CollectionReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: DocumentReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collectionGroup(firestore: Firestore, collectionId: string): Query; + +// @public +export class CollectionReference extends Query { + get id(): string; + get parent(): DocumentReference | null; + get path(): string; + readonly type = "collection"; + withConverter(converter: FirestoreDataConverter): CollectionReference; + withConverter(converter: null): CollectionReference; +} + +// @public +export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: { + mockUserToken?: EmulatorMockTokenOptions | string; +}): void; + +// @public +export function deleteDoc(reference: DocumentReference): Promise; + +// @public +export function deleteField(): FieldValue; + +// @public +export function disableNetwork(firestore: Firestore): Promise; + +// @public +export function doc(firestore: Firestore, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: CollectionReference, path?: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: DocumentReference, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export interface DocumentChange { + readonly doc: QueryDocumentSnapshot; + readonly newIndex: number; + readonly oldIndex: number; + readonly type: DocumentChangeType; +} + +// @public +export type DocumentChangeType = 'added' | 'removed' | 'modified'; + +// @public +export interface DocumentData { + [field: string]: any; +} + +// @public +export function documentId(): FieldPath; + +// @public +export class DocumentReference { + readonly converter: FirestoreDataConverter | null; + readonly firestore: Firestore; + get id(): string; + get parent(): CollectionReference; + get path(): string; + readonly type = "document"; + withConverter(converter: FirestoreDataConverter): DocumentReference; + withConverter(converter: null): DocumentReference; +} + +// @public +export class DocumentSnapshot { + protected constructor(); + data(options?: SnapshotOptions): T | undefined; + exists(): this is QueryDocumentSnapshot; + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; + get id(): string; + readonly metadata: SnapshotMetadata; + get ref(): DocumentReference; +} + +export { EmulatorMockTokenOptions } + +// @public +export function enableIndexedDbPersistence(firestore: Firestore, persistenceSettings?: PersistenceSettings): Promise; + +// @public +export function enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise; + +// @public +export function enableNetwork(firestore: Firestore): Promise; + +// @public +export function endAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endBefore(...fieldValues: unknown[]): QueryConstraint; + +// @public +export class FieldPath { + constructor(...fieldNames: string[]); + isEqual(other: FieldPath): boolean; +} + +// @public +export abstract class FieldValue { + abstract isEqual(other: FieldValue): boolean; +} + +// @public +export class Firestore { + get app(): FirebaseApp; + toJSON(): object; + type: 'firestore-lite' | 'firestore'; +} + +// @public +export interface FirestoreDataConverter { + fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): T; + toFirestore(modelObject: WithFieldValue): DocumentData; + toFirestore(modelObject: PartialWithFieldValue, options: SetOptions): DocumentData; +} + +// @public +export class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; +} + +// @public +export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; + +// @public +export interface FirestoreSettings { + cacheSizeBytes?: number; + experimentalAutoDetectLongPolling?: boolean; + experimentalForceLongPolling?: boolean; + host?: string; + ignoreUndefinedProperties?: boolean; + ssl?: boolean; +} + +// @public +export class GeoPoint { + constructor(latitude: number, longitude: number); + isEqual(other: GeoPoint): boolean; + get latitude(): number; + get longitude(): number; + toJSON(): { + latitude: number; + longitude: number; + }; +} + +// @public +export function getDoc(reference: DocumentReference): Promise>; + +// @public +export function getDocFromCache(reference: DocumentReference): Promise>; + +// @public +export function getDocFromServer(reference: DocumentReference): Promise>; + +// @public +export function getDocs(query: Query): Promise>; + +// @public +export function getDocsFromCache(query: Query): Promise>; + +// @public +export function getDocsFromServer(query: Query): Promise>; + +// @public +export function getFirestore(app?: FirebaseApp): Firestore; + +// @public +export function increment(n: number): FieldValue; + +// @public +export function initializeFirestore(app: FirebaseApp, settings: FirestoreSettings): Firestore; + +// @public +export function limit(limit: number): QueryConstraint; + +// @public +export function limitToLast(limit: number): QueryConstraint; + +// @public +export function loadBundle(firestore: Firestore, bundleData: ReadableStream | ArrayBuffer | string): LoadBundleTask; + +// @public +export class LoadBundleTask implements PromiseLike { + catch(onRejected: (a: Error) => R | PromiseLike): Promise; + onProgress(next?: (progress: LoadBundleTaskProgress) => unknown, error?: (err: Error) => unknown, complete?: () => void): void; + then(onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, onRejected?: (a: Error) => R | PromiseLike): Promise; +} + +// @public +export interface LoadBundleTaskProgress { + bytesLoaded: number; + documentsLoaded: number; + taskState: TaskState; + totalBytes: number; + totalDocuments: number; +} + +export { LogLevel } + +// @public +export function namedQuery(firestore: Firestore, name: string): Promise; + +// @public +export type NestedUpdateFields> = UnionToIntersection<{ + [K in keyof T & string]: T[K] extends Record ? AddPrefixToKeys> : never; +}[keyof T & string]>; + +// @public +export function onSnapshot(reference: DocumentReference, observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(query: Query, observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(query: Query, options: SnapshotListenOptions, observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(query: Query, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshotsInSync(firestore: Firestore, observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshotsInSync(firestore: Firestore, onSync: () => void): Unsubscribe; + +// @public +export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; + +// @public +export type OrderByDirection = 'desc' | 'asc'; + +// @public +export type PartialWithFieldValue = T extends Primitive ? T : T extends {} ? { + [K in keyof T]?: PartialWithFieldValue | FieldValue; +} : Partial; + +// @public +export interface PersistenceSettings { + forceOwnership?: boolean; +} + +// @public +export type Primitive = string | number | boolean | undefined | null; + +// @public +export class Query { + protected constructor(); + readonly converter: FirestoreDataConverter | null; + readonly firestore: Firestore; + readonly type: 'query' | 'collection'; + withConverter(converter: null): Query; + withConverter(converter: FirestoreDataConverter): Query; +} + +// @public +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; + +// @public +export abstract class QueryConstraint { + abstract readonly type: QueryConstraintType; +} + +// @public +export type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; + +// @public +export class QueryDocumentSnapshot extends DocumentSnapshot { + // @override + data(options?: SnapshotOptions): T; +} + +// @public +export function queryEqual(left: Query, right: Query): boolean; + +// @public +export class QuerySnapshot { + docChanges(options?: SnapshotListenOptions): Array>; + get docs(): Array>; + get empty(): boolean; + forEach(callback: (result: QueryDocumentSnapshot) => void, thisArg?: unknown): void; + readonly metadata: SnapshotMetadata; + readonly query: Query; + get size(): number; +} + +// @public +export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; + +// @public +export function runTransaction(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise): Promise; + +// @public +export function serverTimestamp(): FieldValue; + +// @public +export function setDoc(reference: DocumentReference, data: WithFieldValue): Promise; + +// @public +export function setDoc(reference: DocumentReference, data: PartialWithFieldValue, options: SetOptions): Promise; + +// @public +export function setLogLevel(logLevel: LogLevel): void; + +// @public +export type SetOptions = { + readonly merge?: boolean; +} | { + readonly mergeFields?: Array; +}; + +// @public +export function snapshotEqual(left: DocumentSnapshot | QuerySnapshot, right: DocumentSnapshot | QuerySnapshot): boolean; + +// @public +export interface SnapshotListenOptions { + readonly includeMetadataChanges?: boolean; +} + +// @public +export class SnapshotMetadata { + readonly fromCache: boolean; + readonly hasPendingWrites: boolean; + isEqual(other: SnapshotMetadata): boolean; +} + +// @public +export interface SnapshotOptions { + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +} + +// @public +export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAfter(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function startAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export type TaskState = 'Error' | 'Running' | 'Success'; + +// @public +export function terminate(firestore: Firestore): Promise; + +// @public +export class Timestamp { + constructor( + seconds: number, + nanoseconds: number); + static fromDate(date: Date): Timestamp; + static fromMillis(milliseconds: number): Timestamp; + isEqual(other: Timestamp): boolean; + readonly nanoseconds: number; + static now(): Timestamp; + readonly seconds: number; + toDate(): Date; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + toMillis(): number; + toString(): string; + valueOf(): string; +} + +// @public +export class Transaction { + delete(documentRef: DocumentReference): this; + get(documentRef: DocumentReference): Promise>; + set(documentRef: DocumentReference, data: WithFieldValue): this; + set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): this; + update(documentRef: DocumentReference, data: UpdateData): this; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; +} + +// @public +export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + +// @public +export interface Unsubscribe { + (): void; +} + +// @public +export type UpdateData = T extends Primitive ? T : T extends Map ? Map, UpdateData> : T extends {} ? { + [K in keyof T]?: UpdateData | FieldValue; +} & NestedUpdateFields : Partial; + +// @public +export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; + +// @public +export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; + +// @public +export function waitForPendingWrites(firestore: Firestore): Promise; + +// @public +export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryConstraint; + +// @public +export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; + +// @public +export type WithFieldValue = T extends Primitive ? T : T extends {} ? { + [K in keyof T]: WithFieldValue | FieldValue; +} : Partial; + +// @public +export class WriteBatch { + commit(): Promise; + delete(documentRef: DocumentReference): WriteBatch; + set(documentRef: DocumentReference, data: WithFieldValue): WriteBatch; + set(documentRef: DocumentReference, data: PartialWithFieldValue, options: SetOptions): WriteBatch; + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; +} + +// @public +export function writeBatch(firestore: Firestore): WriteBatch; + + +``` diff --git a/common/api-review/functions-exp.api.md b/common/api-review/functions.api.md similarity index 91% rename from common/api-review/functions-exp.api.md rename to common/api-review/functions.api.md index c10af4b0f6c..36bacc8f443 100644 --- a/common/api-review/functions-exp.api.md +++ b/common/api-review/functions.api.md @@ -1,49 +1,49 @@ -## API Report File for "@firebase/functions-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { FirebaseApp } from '@firebase/app-exp'; -import { FirebaseError } from '@firebase/util'; - -// @public -export function connectFunctionsEmulator(functionsInstance: Functions, host: string, port: number): void; - -// @public -export interface Functions { - app: FirebaseApp; - customDomain: string | null; - region: string; -} - -// @public -export interface FunctionsError extends FirebaseError { - readonly code: FunctionsErrorCode; - readonly details?: unknown; -} - -// @public -export type FunctionsErrorCode = 'ok' | 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; - -// @public -export function getFunctions(app?: FirebaseApp, regionOrCustomDomain?: string): Functions; - -// @public -export type HttpsCallable = (data?: RequestData | null) => Promise>; - -// @public -export function httpsCallable(functionsInstance: Functions, name: string, options?: HttpsCallableOptions): HttpsCallable; - -// @public -export interface HttpsCallableOptions { - timeout?: number; -} - -// @public -export interface HttpsCallableResult { - readonly data: ResponseData; -} - - -``` +## API Report File for "@firebase/functions" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; +import { FirebaseError } from '@firebase/util'; + +// @public +export function connectFunctionsEmulator(functionsInstance: Functions, host: string, port: number): void; + +// @public +export interface Functions { + app: FirebaseApp; + customDomain: string | null; + region: string; +} + +// @public +export interface FunctionsError extends FirebaseError { + readonly code: FunctionsErrorCode; + readonly details?: unknown; +} + +// @public +export type FunctionsErrorCode = 'ok' | 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; + +// @public +export function getFunctions(app?: FirebaseApp, regionOrCustomDomain?: string): Functions; + +// @public +export type HttpsCallable = (data?: RequestData | null) => Promise>; + +// @public +export function httpsCallable(functionsInstance: Functions, name: string, options?: HttpsCallableOptions): HttpsCallable; + +// @public +export interface HttpsCallableOptions { + timeout?: number; +} + +// @public +export interface HttpsCallableResult { + readonly data: ResponseData; +} + + +``` diff --git a/common/api-review/installations-exp.api.md b/common/api-review/installations.api.md similarity index 85% rename from common/api-review/installations-exp.api.md rename to common/api-review/installations.api.md index dd3b285afc7..4b1465b1cb8 100644 --- a/common/api-review/installations-exp.api.md +++ b/common/api-review/installations.api.md @@ -1,10 +1,10 @@ -## API Report File for "@firebase/installations-exp" +## API Report File for "@firebase/installations" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; // @public export function deleteInstallations(installations: Installations): Promise; @@ -32,6 +32,7 @@ export type IdChangeUnsubscribeFn = () => void; // @public export interface Installations { + app: FirebaseApp; } // @public diff --git a/common/api-review/messaging-exp.sw.api.md b/common/api-review/messaging-sw.api.md similarity index 52% rename from common/api-review/messaging-exp.sw.api.md rename to common/api-review/messaging-sw.api.md index a0e2f9e8a8b..bff14180926 100644 --- a/common/api-review/messaging-exp.sw.api.md +++ b/common/api-review/messaging-sw.api.md @@ -1,56 +1,69 @@ -## API Report File for "@firebase/messaging-exp/sw" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { FirebaseApp } from '@firebase/app-exp'; -import { NextFn } from '@firebase/util'; -import { Observer } from '@firebase/util'; -import { Unsubscribe } from '@firebase/util'; - -// @public -export interface FcmOptions { - analyticsLabel?: string; - link?: string; -} - -// @public -export interface FirebaseMessaging { -} - -// @public -export function getMessaging(app?: FirebaseApp): FirebaseMessaging; - -// @public -export function isSupported(): Promise; - -// @public -export interface MessagePayload { - collapseKey: string; - data?: { - [key: string]: string; - }; - fcmOptions?: FcmOptions; - from: string; - notification?: NotificationPayload; -} - -export { NextFn } - -// @public -export interface NotificationPayload { - body?: string; - image?: string; - title?: string; -} - -export { Observer } - -// @public -export function onBackgroundMessage(messaging: FirebaseMessaging, nextOrObserver: NextFn | Observer): Unsubscribe; - -export { Unsubscribe } - - -``` +## API Report File for "@firebase/messaging-sw" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; +import { NextFn } from '@firebase/util'; +import { Observer } from '@firebase/util'; +import { Unsubscribe } from '@firebase/util'; + +// @public +export function experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging: Messaging, enable: boolean): void; + +// @public +export interface FcmOptions { + analyticsLabel?: string; + link?: string; +} + +// @public +export function getMessaging(app?: FirebaseApp): Messaging; + +// @public +export interface GetTokenOptions { + serviceWorkerRegistration?: ServiceWorkerRegistration; + vapidKey?: string; +} + +// @public +export function isSupported(): Promise; + +// @public +export interface MessagePayload { + collapseKey: string; + data?: { + [key: string]: string; + }; + fcmOptions?: FcmOptions; + from: string; + messageId: string; + notification?: NotificationPayload; +} + +// @public +export interface Messaging { + app: FirebaseApp; +} + +export { NextFn } + +// @public +export interface NotificationPayload { + body?: string; + image?: string; + title?: string; +} + +export { Observer } + +// @public +export function onBackgroundMessage(messaging: Messaging, nextOrObserver: NextFn | Observer): Unsubscribe; + +export { Unsubscribe } + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/common/api-review/messaging-exp.api.md b/common/api-review/messaging.api.md similarity index 88% rename from common/api-review/messaging-exp.api.md rename to common/api-review/messaging.api.md index 85bd0b390ba..f01c41abdac 100644 --- a/common/api-review/messaging-exp.api.md +++ b/common/api-review/messaging.api.md @@ -1,69 +1,70 @@ -## API Report File for "@firebase/messaging-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { FirebaseApp } from '@firebase/app-exp'; -import { NextFn } from '@firebase/util'; -import { Observer } from '@firebase/util'; -import { Unsubscribe } from '@firebase/util'; - -// @public -export function deleteToken(messaging: Messaging): Promise; - -// @public -export interface FcmOptions { - analyticsLabel?: string; - link?: string; -} - -// @public -export function getMessaging(app?: FirebaseApp): Messaging; - -// @public -export function getToken(messaging: Messaging, options?: GetTokenOptions): Promise; - -// @public -export interface GetTokenOptions { - serviceWorkerRegistration?: ServiceWorkerRegistration; - vapidKey?: string; -} - -// @public -export function isSupported(): Promise; - -// @public -export interface MessagePayload { - collapseKey: string; - data?: { - [key: string]: string; - }; - fcmOptions?: FcmOptions; - from: string; - messageId: string; - notification?: NotificationPayload; -} - -// @public -export interface Messaging { -} - -export { NextFn } - -// @public -export interface NotificationPayload { - body?: string; - image?: string; - title?: string; -} - -export { Observer } - -// @public -export function onMessage(messaging: Messaging, nextOrObserver: NextFn | Observer): Unsubscribe; - -export { Unsubscribe } - - -``` +## API Report File for "@firebase/messaging" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; +import { NextFn } from '@firebase/util'; +import { Observer } from '@firebase/util'; +import { Unsubscribe } from '@firebase/util'; + +// @public +export function deleteToken(messaging: Messaging): Promise; + +// @public +export interface FcmOptions { + analyticsLabel?: string; + link?: string; +} + +// @public +export function getMessaging(app?: FirebaseApp): Messaging; + +// @public +export function getToken(messaging: Messaging, options?: GetTokenOptions): Promise; + +// @public +export interface GetTokenOptions { + serviceWorkerRegistration?: ServiceWorkerRegistration; + vapidKey?: string; +} + +// @public +export function isSupported(): Promise; + +// @public +export interface MessagePayload { + collapseKey: string; + data?: { + [key: string]: string; + }; + fcmOptions?: FcmOptions; + from: string; + messageId: string; + notification?: NotificationPayload; +} + +// @public +export interface Messaging { + app: FirebaseApp; +} + +export { NextFn } + +// @public +export interface NotificationPayload { + body?: string; + image?: string; + title?: string; +} + +export { Observer } + +// @public +export function onMessage(messaging: Messaging, nextOrObserver: NextFn | Observer): Unsubscribe; + +export { Unsubscribe } + + +``` diff --git a/common/api-review/performance-exp.api.md b/common/api-review/performance.api.md similarity index 88% rename from common/api-review/performance-exp.api.md rename to common/api-review/performance.api.md index bfc50e9be06..0de7ee63704 100644 --- a/common/api-review/performance-exp.api.md +++ b/common/api-review/performance.api.md @@ -1,13 +1,14 @@ -## API Report File for "@firebase/performance-exp" +## API Report File for "@firebase/performance" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; // @public export interface FirebasePerformance { + app: FirebaseApp; dataCollectionEnabled: boolean; instrumentationEnabled: boolean; } diff --git a/common/api-review/remote-config-exp.api.md b/common/api-review/remote-config.api.md similarity index 90% rename from common/api-review/remote-config-exp.api.md rename to common/api-review/remote-config.api.md index 8a7dec2dadd..8ddf568d0c0 100644 --- a/common/api-review/remote-config-exp.api.md +++ b/common/api-review/remote-config.api.md @@ -1,10 +1,10 @@ -## API Report File for "@firebase/remote-config-exp" +## API Report File for "@firebase/remote-config" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; // @public export function activate(remoteConfig: RemoteConfig): Promise; @@ -44,6 +44,7 @@ export type LogLevel = 'debug' | 'error' | 'silent'; // @public export interface RemoteConfig { + app: FirebaseApp; defaultConfig: { [key: string]: string | number | boolean; }; diff --git a/common/api-review/storage.api.md b/common/api-review/storage.api.md index 92906ced407..d4d6db5f998 100644 --- a/common/api-review/storage.api.md +++ b/common/api-review/storage.api.md @@ -21,6 +21,11 @@ export function connectStorageEmulator(storage: FirebaseStorage, host: string, p mockUserToken?: EmulatorMockTokenOptions | string; }): void; +// Warning: (ae-forgotten-export) The symbol "StringData" needs to be exported by the entry point index.d.ts +// +// @internal (undocumented) +export function _dataFromString(format: StringFormat, stringData: string): StringData; + // @public export function deleteObject(ref: StorageReference): Promise; @@ -46,9 +51,51 @@ export interface FirebaseStorage extends _FirebaseService { maxUploadRetryTime: number; } -// @public -export interface FirebaseStorageError extends FirebaseError { - serverResponse: string | null; +// @internal +export class _FirebaseStorageImpl implements FirebaseStorage { + constructor( + app: FirebaseApp, _authProvider: Provider, + _appCheckProvider: Provider, + _pool: ConnectionPool, _url?: string | undefined, _firebaseVersion?: string | undefined); + readonly app: FirebaseApp; + // (undocumented) + readonly _appCheckProvider: Provider; + // (undocumented) + protected readonly _appId: string | null; + // (undocumented) + readonly _authProvider: Provider; + // (undocumented) + _bucket: _Location | null; + _delete(): Promise; + // (undocumented) + readonly _firebaseVersion?: string | undefined; + // (undocumented) + _getAppCheckToken(): Promise; + // (undocumented) + _getAuthToken(): Promise; + // (undocumented) + get host(): string; + set host(host: string); + // Warning: (ae-forgotten-export) The symbol "RequestInfo" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "Request" needs to be exported by the entry point index.d.ts + // + // (undocumented) + _makeRequest(requestInfo: RequestInfo_2, authToken: string | null, appCheckToken: string | null): Request_2; + // (undocumented) + makeRequestWithTokens(requestInfo: RequestInfo_2): Promise>; + _makeStorageReference(loc: _Location): _Reference; + get maxOperationRetryTime(): number; + set maxOperationRetryTime(time: number); + get maxUploadRetryTime(): number; + set maxUploadRetryTime(time: number); + // (undocumented) + _overrideAuthToken?: string; + // Warning: (ae-forgotten-export) The symbol "ConnectionPool" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly _pool: ConnectionPool; + // (undocumented) + readonly _url?: string | undefined; } // @public @@ -77,6 +124,14 @@ export function getMetadata(ref: StorageReference): Promise; // @public export function getStorage(app?: FirebaseApp, bucketUrl?: string): FirebaseStorage; +// Warning: (ae-forgotten-export) The symbol "StorageError" needs to be exported by the entry point index.d.ts +// +// @internal (undocumented) +export function _invalidArgument(message: string): StorageError_2; + +// @internal (undocumented) +export function _invalidRootOperation(name: string): StorageError_2; + // @public export function list(ref: StorageReference, options?: ListOptions): Promise; @@ -123,18 +178,17 @@ export function ref(storageOrRef: FirebaseStorage | StorageReference, path?: str // @internal export class _Reference { - // Warning: (ae-forgotten-export) The symbol "FirebaseStorageImpl" needs to be exported by the entry point index.d.ts - constructor(_service: FirebaseStorageImpl, location: string | _Location); + constructor(_service: _FirebaseStorageImpl, location: string | _Location); get bucket(): string; get fullPath(): string; // (undocumented) _location: _Location; get name(): string; // (undocumented) - protected _newRef(service: FirebaseStorageImpl, location: _Location): _Reference; + protected _newRef(service: _FirebaseStorageImpl, location: _Location): _Reference; get parent(): _Reference | null; get root(): _Reference; - get storage(): FirebaseStorageImpl; + get storage(): _FirebaseStorageImpl; _throwIfRoot(name: string): void; // @override toString(): string; @@ -152,12 +206,17 @@ export interface SettableMetadata { } | undefined; } +// @public +export interface StorageError extends FirebaseError { + serverResponse: string | null; +} + // @public export interface StorageObserver { // (undocumented) complete?: CompleteFn | null; // (undocumented) - error?: (error: FirebaseStorageError) => void | null; + error?: (error: StorageError) => void | null; // (undocumented) next?: NextFn | null; } @@ -174,22 +233,42 @@ export interface StorageReference { } // @public -export type StringFormat = string; +export type StringFormat = typeof StringFormat[keyof typeof StringFormat]; // @public export const StringFormat: { - RAW: string; - BASE64: string; - BASE64URL: string; - DATA_URL: string; + readonly RAW: "raw"; + readonly BASE64: "base64"; + readonly BASE64URL: "base64url"; + readonly DATA_URL: "data_url"; }; // @public export type TaskEvent = 'state_changed'; +// @internal +export type _TaskEvent = string; + +// @internal +export const _TaskEvent: { + STATE_CHANGED: string; +}; + // @public export type TaskState = 'running' | 'paused' | 'success' | 'canceled' | 'error'; +// @internal +export type _TaskState = typeof _TaskState[keyof typeof _TaskState]; + +// @internal +export const _TaskState: { + readonly RUNNING: "running"; + readonly PAUSED: "paused"; + readonly SUCCESS: "success"; + readonly CANCELED: "canceled"; + readonly ERROR: "error"; +}; + // @public export function updateMetadata(ref: StorageReference, metadata: SettableMetadata): Promise; @@ -216,12 +295,12 @@ export function uploadString(ref: StorageReference, value: string, format?: stri // @public export interface UploadTask { cancel(): boolean; - catch(onRejected: (error: FirebaseStorageError) => unknown): Promise; - on(event: TaskEvent, nextOrObserver?: StorageObserver | null | ((snapshot: UploadTaskSnapshot) => unknown), error?: ((a: FirebaseStorageError) => unknown) | null, complete?: Unsubscribe | null): Unsubscribe | Subscribe; + catch(onRejected: (error: StorageError) => unknown): Promise; + on(event: TaskEvent, nextOrObserver?: StorageObserver | null | ((snapshot: UploadTaskSnapshot) => unknown), error?: ((a: StorageError) => unknown) | null, complete?: Unsubscribe | null): Unsubscribe | Subscribe; pause(): boolean; resume(): boolean; snapshot: UploadTaskSnapshot; - then(onFulfilled?: ((snapshot: UploadTaskSnapshot) => unknown) | null, onRejected?: ((error: FirebaseStorageError) => unknown) | null): Promise; + then(onFulfilled?: ((snapshot: UploadTaskSnapshot) => unknown) | null, onRejected?: ((error: StorageError) => unknown) | null): Promise; } // @internal @@ -229,24 +308,18 @@ export class _UploadTask { constructor(ref: _Reference, blob: _FbsBlob, metadata?: Metadata | null); _blob: _FbsBlob; cancel(): boolean; - catch(onRejected: (p1: FirebaseStorageError_2) => T | Promise): Promise; + catch(onRejected: (p1: StorageError_2) => T | Promise): Promise; // Warning: (ae-forgotten-export) The symbol "Metadata" needs to be exported by the entry point index.d.ts _metadata: Metadata | null; - // Warning: (ae-forgotten-export) The symbol "TaskEvent" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "StorageObserver" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "ErrorFn" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "CompleteFn" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Unsubscribe" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Subscribe" needs to be exported by the entry point index.d.ts - on(type: TaskEvent_2, nextOrObserver?: StorageObserver_2 | ((a: UploadTaskSnapshot_2) => unknown), error?: ErrorFn, completed?: CompleteFn_2): Unsubscribe_2 | Subscribe_2; + on(type: _TaskEvent, nextOrObserver?: StorageObserver | null | ((snapshot: UploadTaskSnapshot) => unknown), error?: ((a: StorageError_2) => unknown) | null, completed?: Unsubscribe_2 | null): Unsubscribe_2 | Subscribe_2; pause(): boolean; resume(): boolean; - // Warning: (ae-forgotten-export) The symbol "UploadTaskSnapshot" needs to be exported by the entry point index.d.ts - get snapshot(): UploadTaskSnapshot_2; + get snapshot(): UploadTaskSnapshot; // Warning: (ae-forgotten-export) The symbol "InternalTaskState" needs to be exported by the entry point index.d.ts _state: InternalTaskState; - // Warning: (ae-forgotten-export) The symbol "FirebaseStorageError" needs to be exported by the entry point index.d.ts - then(onFulfilled?: ((value: UploadTaskSnapshot_2) => U | Promise) | null, onRejected?: ((error: FirebaseStorageError_2) => U | Promise) | null): Promise; + then(onFulfilled?: ((value: UploadTaskSnapshot) => U | Promise) | null, onRejected?: ((error: StorageError_2) => U | Promise) | null): Promise; _transferred: number; } diff --git a/config/webpack.test.js b/config/webpack.test.js index 3612cc3db21..b19f2983b26 100644 --- a/config/webpack.test.js +++ b/config/webpack.test.js @@ -82,7 +82,7 @@ module.exports = { { test: /\.js$/, include: function (modulePath) { - const match = /node_modules\/@firebase.*/.test(modulePath); + const match = /node_modules\/@firebase.*/.test(modulePath) && !modulePath.includes('-compat'); return match; }, use: { diff --git a/integration/compat-interop/analytics.test.ts b/integration/compat-interop/analytics.test.ts index 637e533286d..51055ae0fa1 100644 --- a/integration/compat-interop/analytics.test.ts +++ b/integration/compat-interop/analytics.test.ts @@ -17,7 +17,7 @@ import { getModularInstance } from '@firebase/util'; import { expect } from 'chai'; -import { getAnalytics } from '@firebase/analytics-exp'; +import { getAnalytics } from '@firebase/analytics'; import firebase from '@firebase/app-compat'; import '@firebase/analytics-compat'; diff --git a/integration/compat-interop/app.test.ts b/integration/compat-interop/app.test.ts index 4a79c4b5bc3..ef2196a7809 100644 --- a/integration/compat-interop/app.test.ts +++ b/integration/compat-interop/app.test.ts @@ -17,7 +17,7 @@ import { getModularInstance } from '@firebase/util'; import { expect } from 'chai'; -import { getApp, getApps } from '@firebase/app-exp'; +import { getApp, getApps } from '@firebase/app'; import firebase from '@firebase/app-compat'; import { TEST_PROJECT_CONFIG } from './util'; diff --git a/integration/compat-interop/auth.test.ts b/integration/compat-interop/auth.test.ts index b15b368ed22..5cc6950850d 100644 --- a/integration/compat-interop/auth.test.ts +++ b/integration/compat-interop/auth.test.ts @@ -17,7 +17,7 @@ import { getModularInstance } from '@firebase/util'; import { expect } from 'chai'; -import { getAuth, signOut } from '@firebase/auth-exp'; +import { getAuth, signOut } from '@firebase/auth'; import firebase from '@firebase/app-compat'; import '@firebase/auth-compat'; diff --git a/integration/compat-interop/functions.test.ts b/integration/compat-interop/functions.test.ts index 327fd8bde94..00de38c268e 100644 --- a/integration/compat-interop/functions.test.ts +++ b/integration/compat-interop/functions.test.ts @@ -17,7 +17,7 @@ import { getModularInstance } from '@firebase/util'; import { expect } from 'chai'; -import { getFunctions } from '@firebase/functions-exp'; +import { getFunctions } from '@firebase/functions'; import firebase from '@firebase/app-compat'; import '@firebase/functions-compat'; diff --git a/integration/compat-interop/messaging.test.ts b/integration/compat-interop/messaging.test.ts index 88758d2e3e3..a4937a7985f 100644 --- a/integration/compat-interop/messaging.test.ts +++ b/integration/compat-interop/messaging.test.ts @@ -17,7 +17,7 @@ import { getModularInstance } from '@firebase/util'; import { expect } from 'chai'; -import { getMessaging } from '@firebase/messaging-exp'; +import { getMessaging } from '@firebase/messaging'; import firebase from '@firebase/app-compat'; import '@firebase/messaging-compat'; diff --git a/integration/compat-interop/package.json b/integration/compat-interop/package.json index eb0f2fed166..31049586736 100644 --- a/integration/compat-interop/package.json +++ b/integration/compat-interop/package.json @@ -8,19 +8,19 @@ "test:debug": "karma start --browsers Chrome --auto-watch" }, "dependencies": { - "@firebase/app-exp": "0.0.900", + "@firebase/app": "0.6.30", "@firebase/app-compat": "0.0.900", - "@firebase/analytics-exp": "0.0.900", + "@firebase/analytics": "0.6.18", "@firebase/analytics-compat": "0.0.900", - "@firebase/auth-exp": "0.0.900", + "@firebase/auth": "0.16.8", "@firebase/auth-compat": "0.0.900", - "@firebase/functions-exp": "0.0.900", + "@firebase/functions": "0.6.15", "@firebase/functions-compat": "0.0.900", - "@firebase/messaging-exp": "0.0.900", + "@firebase/messaging": "0.8.0", "@firebase/messaging-compat": "0.0.900", - "@firebase/performance-exp": "0.0.900", + "@firebase/performance": "0.4.18", "@firebase/performance-compat": "0.0.900", - "@firebase/remote-config-exp": "0.0.900", + "@firebase/remote-config": "0.1.43", "@firebase/remote-config-compat": "0.0.900" }, "devDependencies": { diff --git a/integration/compat-interop/performance.test.ts b/integration/compat-interop/performance.test.ts index 6820b0cb368..47f357fdade 100644 --- a/integration/compat-interop/performance.test.ts +++ b/integration/compat-interop/performance.test.ts @@ -17,7 +17,7 @@ import { getModularInstance } from '@firebase/util'; import { expect } from 'chai'; -import { getPerformance } from '@firebase/performance-exp'; +import { getPerformance } from '@firebase/performance'; import firebase from '@firebase/app-compat'; import '@firebase/performance-compat'; diff --git a/integration/compat-interop/remote-config.test.ts b/integration/compat-interop/remote-config.test.ts index 580832aee12..9f7983c106e 100644 --- a/integration/compat-interop/remote-config.test.ts +++ b/integration/compat-interop/remote-config.test.ts @@ -17,7 +17,7 @@ import { getModularInstance } from '@firebase/util'; import { expect } from 'chai'; -import { getRemoteConfig } from '@firebase/remote-config-exp'; +import { getRemoteConfig } from '@firebase/remote-config'; import firebase from '@firebase/app-compat'; import '@firebase/remote-config-compat'; diff --git a/integration/compat-typings/package.json b/integration/compat-typings/package.json index ec6f6257eb8..e29397124a9 100644 --- a/integration/compat-typings/package.json +++ b/integration/compat-typings/package.json @@ -7,7 +7,7 @@ "test:ci": "node ../../scripts/run_tests_in_ci.js -s test" }, "dependencies": { - "firebase-exp": "file:../../packages-exp/firebase-exp" + "firebase": "*" }, "devDependencies": { "typescript": "4.2.2" diff --git a/integration/compat-typings/typings.ts b/integration/compat-typings/typings.ts index 4bfa5152089..171d0a74a0f 100644 --- a/integration/compat-typings/typings.ts +++ b/integration/compat-typings/typings.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import firebase from 'firebase-exp/compat'; +import firebase from 'firebase/compat'; import { FirebaseAuth, User } from '@firebase/auth-types'; import { FirebaseAnalytics } from '@firebase/analytics-types'; import { FirebaseApp } from '@firebase/app-compat'; @@ -28,7 +28,7 @@ import { FirebaseFunctions } from '@firebase/functions-types'; import { FirebaseInstallations } from '@firebase/installations-types'; // Get type directly from messaging package, messaging-compat does not implement // the current messaging API. -import { MessagingCompat } from '../../packages-exp/messaging-compat/src/messaging-compat'; +import { MessagingCompat } from '../../packages/messaging-compat/src/messaging-compat'; import { FirebasePerformance } from '@firebase/performance-types'; import { RemoteConfig } from '@firebase/remote-config-types'; import { diff --git a/integration/firebase/test/namespace.test.ts b/integration/firebase/test/namespace.test.ts index 42d4eec6390..a0f1c782292 100644 --- a/integration/firebase/test/namespace.test.ts +++ b/integration/firebase/test/namespace.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import firebase from 'firebase'; +import firebase from 'firebase/compat'; import * as namespaceDefinition from './namespaceDefinition.json'; import validateNamespace from './validator'; diff --git a/integration/firebase/test/namespaceDefinition.json b/integration/firebase/test/namespaceDefinition.json index 3f4bb783cc9..cc3b8f3a2e8 100644 --- a/integration/firebase/test/namespaceDefinition.json +++ b/integration/firebase/test/namespaceDefinition.json @@ -55,9 +55,6 @@ "removeApp": { "__type": "function" }, - "components": { - "__type": "Map" - }, "ErrorFactory": { "__type": "function" }, @@ -223,16 +220,10 @@ "onMessage": { "__type": "function" }, - "onTokenRefresh": { - "__type": "function" - }, - "requestPermission": { - "__type": "function" - }, "deleteToken": { "__type": "function" }, - "setBackgroundMessageHandler": { + "onBackgroundMessage": { "__type": "function" } } diff --git a/integration/firestore/firebase_export.ts b/integration/firestore/firebase_export.ts index 92d28a23709..f651bdaf839 100644 --- a/integration/firestore/firebase_export.ts +++ b/integration/firestore/firebase_export.ts @@ -15,9 +15,8 @@ * limitations under the License. */ -import firebase from '@firebase/app'; -import '@firebase/firestore'; -import '@firebase/firestore/bundle'; +import firebase from '@firebase/app-compat'; +import '@firebase/firestore-compat'; import { FirebaseApp } from '@firebase/app-types'; import { Settings, FirebaseFirestore } from '@firebase/firestore-types'; diff --git a/integration/firestore/firebase_export_memory.ts b/integration/firestore/firebase_export_memory.ts deleted file mode 100644 index 063113e7409..00000000000 --- a/integration/firestore/firebase_export_memory.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '@firebase/app'; -import '@firebase/firestore/memory'; -import '@firebase/firestore/memory-bundle'; -import { FirebaseApp } from '@firebase/app-types'; -import { Settings, FirebaseFirestore } from '@firebase/firestore-types'; - -// This file replaces "packages/firestore/test/integration/util/firebase_export" -// and depends on the minified sources. - -let appCount = 0; - -export function newTestFirestore( - projectId: string, - nameOrApp?: string | FirebaseApp, - settings?: Settings -): FirebaseFirestore { - if (nameOrApp === undefined) { - nameOrApp = 'test-app-' + appCount++; - } - const app = - typeof nameOrApp === 'string' - ? firebase.initializeApp({ apiKey: 'fake-api-key', projectId }, nameOrApp) - : nameOrApp; - - const firestore = firebase.firestore(app); - if (settings) { - firestore.settings(settings); - } - return firestore; -} - -export function usesFunctionalApi(): false { - return false; -} - -const Blob = firebase.firestore.Blob; -const DocumentReference = firebase.firestore.DocumentReference; -const FieldPath = firebase.firestore.FieldPath; -const FieldValue = firebase.firestore.FieldValue; -const Firestore = firebase.firestore.Firestore; -const GeoPoint = firebase.firestore.GeoPoint; -const QueryDocumentSnapshot = firebase.firestore.QueryDocumentSnapshot; -const Timestamp = firebase.firestore.Timestamp; - -export { - Blob, - DocumentReference, - FieldPath, - FieldValue, - Firestore, - GeoPoint, - QueryDocumentSnapshot, - Timestamp -}; diff --git a/integration/firestore/gulpfile.js b/integration/firestore/gulpfile.js index 83b3b13f86f..62a0c470e9f 100644 --- a/integration/firestore/gulpfile.js +++ b/integration/firestore/gulpfile.js @@ -63,10 +63,7 @@ function copyTests() { */ /import\s+\* as firebaseExport\s+from\s+('|")[^\1]+firebase_export\1;?/, `import * as firebaseExport from '${resolve( - __dirname, - isPersistenceEnabled() - ? './firebase_export' - : './firebase_export_memory' + __dirname, './firebase_export' )}'; if (typeof process === 'undefined') { diff --git a/integration/firestore/package.json b/integration/firestore/package.json index 87e7910084c..844c101cde6 100644 --- a/integration/firestore/package.json +++ b/integration/firestore/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@firebase/app": "0.6.30", - "@firebase/firestore": "2.4.0", + "@firebase/firestore-compat": "0.0.900", "@types/mocha": "8.2.3", "gulp": "4.0.2", "gulp-filter": "7.0.0", diff --git a/lerna.json b/lerna.json index 9192331da41..1d51eb2d1c0 100644 --- a/lerna.json +++ b/lerna.json @@ -3,7 +3,6 @@ "npmClient": "yarn", "packages": [ "packages/*", - "packages-exp/*", "integration/*", "repo-scripts/*" ], diff --git a/package.json b/package.json index a2aec745475..deeafa8ce7f 100644 --- a/package.json +++ b/package.json @@ -22,20 +22,16 @@ ], "scripts": { "dev": "lerna run --parallel --scope @firebase/* --scope firebase dev", - "build": "lerna run --scope @firebase/app-exp build:deps && lerna run --scope @firebase/* --scope firebase --ignore @firebase/app-exp build", - "build:exp": "lerna run --scope @firebase/*-exp --scope @firebase/*-compat --scope firebase-exp build", - "build:release": "lerna run --scope @firebase/app-exp build:deps && lerna run --scope @firebase/* --scope firebase --ignore @firebase/*-exp --ignore @firebase/*-compat build", + "build": "lerna run --scope @firebase/* --scope firebase build", "build:changed": "ts-node-script scripts/ci-test/build_changed.ts", + "release:prepare": "lerna run --scope @firebase/* add-compat-overloads && lerna run --scope @firebase/* typings:public", "link:packages": "lerna exec --scope @firebase/* --scope firebase -- yarn link", "stage:packages": "./scripts/prepublish.sh", "repl": "node tools/repl.js", "release": "ts-node-script scripts/release/cli.ts", - "release:exp": "ts-node-script scripts/exp/release.ts", "pretest": "node tools/pretest.js", - "test": "lerna run --concurrency 4 --stream test", + "test": "lerna run --ignore firebase-repo-scripts-prune-dts --ignore firebase-messaging-integration-test --concurrency 4 --stream test", "test:ci": "lerna run --concurrency 4 test:ci", - "test:release": "lerna run --concurrency 4 --ignore @firebase/*-exp --ignore firebase-exp --ignore @firebase/*-compat test:ci", - "test:exp": "lerna run --concurrency 4 --scope @firebase/*-exp --scope firebase-exp --scope @firebase/*-compat --stream test", "pretest:coverage": "mkdirp coverage", "ci:coverage": "lcov-result-merger 'packages/**/lcov.info' 'lcov-all.info'", "test:coverage": "lcov-result-merger 'packages/**/lcov.info' | coveralls", @@ -50,7 +46,7 @@ "lint:fix": "lerna run --scope @firebase/* lint:fix", "size-report": "ts-node-script scripts/size_report/report_binary_size.ts", "modular-export-size-report": "ts-node-script scripts/size_report/report_modular_export_binary_size.ts", - "api-report": "lerna run --scope @firebase/*-exp --scope @firebase/firestore --scope @firebase/storage --scope @firebase/storage-types --scope @firebase/database api-report", + "api-report": "lerna run --scope @firebase/* api-report", "docgen:exp": "ts-node-script scripts/exp/docgen.ts", "postinstall": "yarn --cwd repo-scripts/changelog-generator build", "sa": "ts-node-script repo-scripts/size-analysis/cli.ts", @@ -62,7 +58,6 @@ }, "workspaces": [ "packages/*", - "packages-exp/*", "integration/*", "repo-scripts/*" ], @@ -165,4 +160,4 @@ "pre-commit": "node tools/gitHooks/precommit.js" } } -} +} \ No newline at end of file diff --git a/packages-exp/analytics-compat/rollup.config.js b/packages-exp/analytics-compat/rollup.config.js deleted file mode 100644 index 9b89b7772af..00000000000 --- a/packages-exp/analytics-compat/rollup.config.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/analytics-compat/rollup.config.release.js b/packages-exp/analytics-compat/rollup.config.release.js deleted file mode 100644 index aed49b162c9..00000000000 --- a/packages-exp/analytics-compat/rollup.config.release.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: id => id === '@firebase/installations' - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: id => id === '@firebase/installations' - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/analytics-compat/rollup.shared.js b/packages-exp/analytics-compat/rollup.shared.js deleted file mode 100644 index ff3cbd71e62..00000000000 --- a/packages-exp/analytics-compat/rollup.shared.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/analytics' -]; - -export const es5BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -export const es2017BuildsNoPlugin = [ - { - /** - * Browser Build - */ - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/analytics-exp/.eslintrc.js b/packages-exp/analytics-exp/.eslintrc.js deleted file mode 100644 index 85388055d9d..00000000000 --- a/packages-exp/analytics-exp/.eslintrc.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -const path = require('path'); - -module.exports = { - 'extends': '../../config/.eslintrc.js', - 'parserOptions': { - 'project': 'tsconfig.json', - 'tsconfigRootDir': __dirname - }, - rules: { - 'import/no-extraneous-dependencies': [ - 'error', - { - 'packageDir': [path.resolve(__dirname, '../../'), __dirname] - } - ] - } -}; diff --git a/packages-exp/analytics-exp/karma.conf.js b/packages-exp/analytics-exp/karma.conf.js deleted file mode 100644 index c6488ea06bd..00000000000 --- a/packages-exp/analytics-exp/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @typescript-eslint/no-require-imports -const karmaBase = require('../../config/karma.base'); - -const files = [`**/*.test.ts`]; - -module.exports = function (config) { - config.set({ - ...karmaBase, - files, - preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, - frameworks: ['mocha'] - }); -}; - -module.exports.files = files; diff --git a/packages-exp/analytics-exp/karma.integration.conf.js b/packages-exp/analytics-exp/karma.integration.conf.js deleted file mode 100644 index 94215252451..00000000000 --- a/packages-exp/analytics-exp/karma.integration.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @typescript-eslint/no-require-imports -const karmaBase = require('../../config/karma.base'); - -const files = [`./testing/integration-tests/integration.ts`]; - -module.exports = function (config) { - config.set({ - ...karmaBase, - files, - preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, - frameworks: ['mocha'] - }); -}; - -module.exports.files = files; diff --git a/packages-exp/analytics-exp/package.json b/packages-exp/analytics-exp/package.json deleted file mode 100644 index 57b4e00b833..00000000000 --- a/packages-exp/analytics-exp/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "@firebase/analytics-exp", - "version": "0.0.900", - "private": true, - "description": "A analytics package for new firebase packages", - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.cjs.js", - "browser": "dist/index.esm2017.js", - "module": "dist/index.esm2017.js", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", - "build:deps": "lerna run --scope @firebase/analytics-exp --include-dependencies build", - "dev": "rollup -c -w", - "test": "run-p lint test:all", - "test:all": "run-p test:browser test:integration", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", - "test:browser": "karma start --single-run --nocache", - "test:integration": "karma start ./karma.integration.conf.js --single-run --nocache", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc", - "typings:public": "node ../../scripts/exp/use_typings.js ./dist/analytics-exp-public.d.ts" - }, - "peerDependencies": { - "@firebase/app-exp": "0.x" - }, - "dependencies": { - "@firebase/installations-exp": "0.0.900", - "@firebase/logger": "0.2.6", - "@firebase/util": "1.3.0", - "@firebase/component": "0.5.6", - "tslib": "^2.1.0" - }, - "license": "Apache-2.0", - "devDependencies": { - "@firebase/app-exp": "0.0.900", - "rollup": "2.52.2", - "@rollup/plugin-commonjs": "17.1.0", - "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "11.2.0", - "rollup-plugin-typescript2": "0.30.0", - "typescript": "4.2.2" - }, - "repository": { - "directory": "packages/analytics", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "typings": "dist/src/index.d.ts", - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - }, - "esm5": "dist/index.esm.js" -} \ No newline at end of file diff --git a/packages-exp/analytics-exp/rollup.config.js b/packages-exp/analytics-exp/rollup.config.js deleted file mode 100644 index 184071d3577..00000000000 --- a/packages-exp/analytics-exp/rollup.config.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/analytics-exp/rollup.config.release.js b/packages-exp/analytics-exp/rollup.config.release.js deleted file mode 100644 index c7f9f69c381..00000000000 --- a/packages-exp/analytics-exp/rollup.config.release.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: id => id === '@firebase/installations' - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: id => id === '@firebase/installations' - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/analytics-exp/rollup.shared.js b/packages-exp/analytics-exp/rollup.shared.js deleted file mode 100644 index 397949ded6b..00000000000 --- a/packages-exp/analytics-exp/rollup.shared.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -export const es5BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -export const es2017BuildsNoPlugin = [ - { - /** - * Browser Build - */ - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/analytics-exp/src/constants.ts b/packages-exp/analytics-exp/src/constants.ts deleted file mode 100644 index 1fdfdd5e523..00000000000 --- a/packages-exp/analytics-exp/src/constants.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Type constant for Firebase Analytics. - */ -export const ANALYTICS_TYPE = 'analytics-exp'; - -// Key to attach FID to in gtag params. -export const GA_FID_KEY = 'firebase_id'; -export const ORIGIN_KEY = 'origin'; - -export const FETCH_TIMEOUT_MILLIS = 60 * 1000; - -export const DYNAMIC_CONFIG_URL = - 'https://firebase.googleapis.com/v1alpha/projects/-/apps/{app-id}/webConfig'; - -export const GTAG_URL = 'https://www.googletagmanager.com/gtag/js'; - -export const enum GtagCommand { - EVENT = 'event', - SET = 'set', - CONFIG = 'config' -} diff --git a/packages-exp/analytics-exp/src/errors.ts b/packages-exp/analytics-exp/src/errors.ts deleted file mode 100644 index 98293447c65..00000000000 --- a/packages-exp/analytics-exp/src/errors.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorFactory, ErrorMap } from '@firebase/util'; - -export const enum AnalyticsError { - ALREADY_EXISTS = 'already-exists', - ALREADY_INITIALIZED = 'already-initialized', - ALREADY_INITIALIZED_SETTINGS = 'already-initialized-settings', - INTEROP_COMPONENT_REG_FAILED = 'interop-component-reg-failed', - INVALID_ANALYTICS_CONTEXT = 'invalid-analytics-context', - INDEXEDDB_UNAVAILABLE = 'indexeddb-unavailable', - FETCH_THROTTLE = 'fetch-throttle', - CONFIG_FETCH_FAILED = 'config-fetch-failed', - NO_API_KEY = 'no-api-key', - NO_APP_ID = 'no-app-id' -} - -const ERRORS: ErrorMap = { - [AnalyticsError.ALREADY_EXISTS]: - 'A Firebase Analytics instance with the appId {$id} ' + - ' already exists. ' + - 'Only one Firebase Analytics instance can be created for each appId.', - [AnalyticsError.ALREADY_INITIALIZED]: - 'initializeAnalytics() cannot be called again with different options than those ' + - 'it was initially called with. It can be called again with the same options to ' + - 'return the existing instance, or getAnalytics() can be used ' + - 'to get a reference to the already-intialized instance.', - [AnalyticsError.ALREADY_INITIALIZED_SETTINGS]: - 'Firebase Analytics has already been initialized.' + - 'settings() must be called before initializing any Analytics instance' + - 'or it will have no effect.', - [AnalyticsError.INTEROP_COMPONENT_REG_FAILED]: - 'Firebase Analytics Interop Component failed to instantiate: {$reason}', - [AnalyticsError.INVALID_ANALYTICS_CONTEXT]: - 'Firebase Analytics is not supported in this environment. ' + - 'Wrap initialization of analytics in analytics.isSupported() ' + - 'to prevent initialization in unsupported environments. Details: {$errorInfo}', - [AnalyticsError.INDEXEDDB_UNAVAILABLE]: - 'IndexedDB unavailable or restricted in this environment. ' + - 'Wrap initialization of analytics in analytics.isSupported() ' + - 'to prevent initialization in unsupported environments. Details: {$errorInfo}', - [AnalyticsError.FETCH_THROTTLE]: - 'The config fetch request timed out while in an exponential backoff state.' + - ' Unix timestamp in milliseconds when fetch request throttling ends: {$throttleEndTimeMillis}.', - [AnalyticsError.CONFIG_FETCH_FAILED]: - 'Dynamic config fetch failed: [{$httpStatus}] {$responseMessage}', - [AnalyticsError.NO_API_KEY]: - 'The "apiKey" field is empty in the local Firebase config. Firebase Analytics requires this field to' + - 'contain a valid API key.', - [AnalyticsError.NO_APP_ID]: - 'The "appId" field is empty in the local Firebase config. Firebase Analytics requires this field to' + - 'contain a valid app ID.' -}; - -interface ErrorParams { - [AnalyticsError.ALREADY_EXISTS]: { id: string }; - [AnalyticsError.INTEROP_COMPONENT_REG_FAILED]: { reason: Error }; - [AnalyticsError.FETCH_THROTTLE]: { throttleEndTimeMillis: number }; - [AnalyticsError.CONFIG_FETCH_FAILED]: { - httpStatus: number; - responseMessage: string; - }; - [AnalyticsError.INVALID_ANALYTICS_CONTEXT]: { errorInfo: string }; - [AnalyticsError.INDEXEDDB_UNAVAILABLE]: { errorInfo: string }; -} - -export const ERROR_FACTORY = new ErrorFactory( - 'analytics', - 'Analytics', - ERRORS -); diff --git a/packages-exp/analytics-exp/src/factory.ts b/packages-exp/analytics-exp/src/factory.ts deleted file mode 100644 index 6d9d54585ed..00000000000 --- a/packages-exp/analytics-exp/src/factory.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { SettingsOptions, Analytics, AnalyticsSettings } from './public-types'; -import { Gtag, DynamicConfig, MinimalDynamicConfig } from './types'; -import { getOrCreateDataLayer, wrapOrCreateGtag } from './helpers'; -import { AnalyticsError, ERROR_FACTORY } from './errors'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; -import { areCookiesEnabled, isBrowserExtension } from '@firebase/util'; -import { _initializeAnalytics } from './initialize-analytics'; -import { logger } from './logger'; -import { FirebaseApp, _FirebaseService } from '@firebase/app-exp'; - -/** - * Analytics Service class. - */ -export class AnalyticsService implements Analytics, _FirebaseService { - constructor(public app: FirebaseApp) {} - _delete(): Promise { - delete initializationPromisesMap[this.app.options.appId!]; - return Promise.resolve(); - } -} - -/** - * Maps appId to full initialization promise. Wrapped gtag calls must wait on - * all or some of these, depending on the call's `send_to` param and the status - * of the dynamic config fetches (see below). - */ -export let initializationPromisesMap: { - [appId: string]: Promise; // Promise contains measurement ID string. -} = {}; - -/** - * List of dynamic config fetch promises. In certain cases, wrapped gtag calls - * wait on all these to be complete in order to determine if it can selectively - * wait for only certain initialization (FID) promises or if it must wait for all. - */ -let dynamicConfigPromisesList: Array< - Promise -> = []; - -/** - * Maps fetched measurementIds to appId. Populated when the app's dynamic config - * fetch completes. If already populated, gtag config calls can use this to - * selectively wait for only this app's initialization promise (FID) instead of all - * initialization promises. - */ -const measurementIdToAppId: { [measurementId: string]: string } = {}; - -/** - * Name for window global data layer array used by GA: defaults to 'dataLayer'. - */ -let dataLayerName: string = 'dataLayer'; - -/** - * Name for window global gtag function used by GA: defaults to 'gtag'. - */ -let gtagName: string = 'gtag'; - -/** - * Reproduction of standard gtag function or reference to existing - * gtag function on window object. - */ -let gtagCoreFunction: Gtag; - -/** - * Wrapper around gtag function that ensures FID is sent with all - * relevant event and config calls. - */ -export let wrappedGtagFunction: Gtag; - -/** - * Flag to ensure page initialization steps (creation or wrapping of - * dataLayer and gtag script) are only run once per page load. - */ -let globalInitDone: boolean = false; - -/** - * For testing - * @internal - */ -export function resetGlobalVars( - newGlobalInitDone = false, - newInitializationPromisesMap = {}, - newDynamicPromises = [] -): void { - globalInitDone = newGlobalInitDone; - initializationPromisesMap = newInitializationPromisesMap; - dynamicConfigPromisesList = newDynamicPromises; - dataLayerName = 'dataLayer'; - gtagName = 'gtag'; -} - -/** - * For testing - * @internal - */ -export function getGlobalVars(): { - initializationPromisesMap: { [appId: string]: Promise }; - dynamicConfigPromisesList: Array< - Promise - >; -} { - return { - initializationPromisesMap, - dynamicConfigPromisesList - }; -} - -/** - * Configures Firebase Analytics to use custom `gtag` or `dataLayer` names. - * Intended to be used if `gtag.js` script has been installed on - * this page independently of Firebase Analytics, and is using non-default - * names for either the `gtag` function or for `dataLayer`. - * Must be called before calling `getAnalytics()` or it won't - * have any effect. - * - * @public - * - * @param options - Custom gtag and dataLayer names. - */ -export function settings(options: SettingsOptions): void { - if (globalInitDone) { - throw ERROR_FACTORY.create(AnalyticsError.ALREADY_INITIALIZED); - } - if (options.dataLayerName) { - dataLayerName = options.dataLayerName; - } - if (options.gtagName) { - gtagName = options.gtagName; - } -} - -/** - * Returns true if no environment mismatch is found. - * If environment mismatches are found, throws an INVALID_ANALYTICS_CONTEXT - * error that also lists details for each mismatch found. - */ -function warnOnBrowserContextMismatch(): void { - const mismatchedEnvMessages = []; - if (isBrowserExtension()) { - mismatchedEnvMessages.push('This is a browser extension environment.'); - } - if (!areCookiesEnabled()) { - mismatchedEnvMessages.push('Cookies are not available.'); - } - if (mismatchedEnvMessages.length > 0) { - const details = mismatchedEnvMessages - .map((message, index) => `(${index + 1}) ${message}`) - .join(' '); - const err = ERROR_FACTORY.create(AnalyticsError.INVALID_ANALYTICS_CONTEXT, { - errorInfo: details - }); - logger.warn(err.message); - } -} - -/** - * Analytics instance factory. - * @internal - */ -export function factory( - app: FirebaseApp, - installations: _FirebaseInstallationsInternal, - options?: AnalyticsSettings -): AnalyticsService { - warnOnBrowserContextMismatch(); - const appId = app.options.appId; - if (!appId) { - throw ERROR_FACTORY.create(AnalyticsError.NO_APP_ID); - } - if (!app.options.apiKey) { - if (app.options.measurementId) { - logger.warn( - `The "apiKey" field is empty in the local Firebase config. This is needed to fetch the latest` + - ` measurement ID for this Firebase app. Falling back to the measurement ID ${app.options.measurementId}` + - ` provided in the "measurementId" field in the local Firebase config.` - ); - } else { - throw ERROR_FACTORY.create(AnalyticsError.NO_API_KEY); - } - } - if (initializationPromisesMap[appId] != null) { - throw ERROR_FACTORY.create(AnalyticsError.ALREADY_EXISTS, { - id: appId - }); - } - - if (!globalInitDone) { - // Steps here should only be done once per page: creation or wrapping - // of dataLayer and global gtag function. - - getOrCreateDataLayer(dataLayerName); - - const { wrappedGtag, gtagCore } = wrapOrCreateGtag( - initializationPromisesMap, - dynamicConfigPromisesList, - measurementIdToAppId, - dataLayerName, - gtagName - ); - wrappedGtagFunction = wrappedGtag; - gtagCoreFunction = gtagCore; - - globalInitDone = true; - } - // Async but non-blocking. - // This map reflects the completion state of all promises for each appId. - initializationPromisesMap[appId] = _initializeAnalytics( - app, - dynamicConfigPromisesList, - measurementIdToAppId, - installations, - gtagCoreFunction, - dataLayerName, - options - ); - - const analyticsInstance: AnalyticsService = new AnalyticsService(app); - - return analyticsInstance; -} diff --git a/packages-exp/analytics-exp/src/functions.test.ts b/packages-exp/analytics-exp/src/functions.test.ts deleted file mode 100644 index bf4f08dbd95..00000000000 --- a/packages-exp/analytics-exp/src/functions.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import '../testing/setup'; -import { - setCurrentScreen, - logEvent, - setUserId, - setUserProperties, - setAnalyticsCollectionEnabled -} from './functions'; -import { GtagCommand } from './constants'; - -const fakeMeasurementId = 'abcd-efgh-ijkl'; -const fakeInitializationPromise = Promise.resolve(fakeMeasurementId); - -describe('FirebaseAnalytics methods', () => { - const gtagStub: SinonStub = stub(); - - afterEach(() => { - gtagStub.reset(); - }); - - it('logEvent() calls gtag function correctly', async () => { - await logEvent(gtagStub, fakeInitializationPromise, 'add_to_cart', { - currency: 'USD' - }); - - expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'add_to_cart', { - 'send_to': fakeMeasurementId, - currency: 'USD' - }); - }); - - it('logEvent() with no event params calls gtag function correctly', async () => { - await logEvent(gtagStub, fakeInitializationPromise, 'view_item'); - - expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'view_item', { - 'send_to': fakeMeasurementId - }); - }); - - it('logEvent() globally calls gtag function correctly', async () => { - await logEvent( - gtagStub, - fakeInitializationPromise, - 'add_to_cart', - { - currency: 'USD' - }, - { global: true } - ); - - expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'add_to_cart', { - currency: 'USD' - }); - }); - - it('logEvent() with no event params globally calls gtag function correctly', async () => { - await logEvent( - gtagStub, - fakeInitializationPromise, - 'add_to_cart', - undefined, - { - global: true - } - ); - - expect(gtagStub).to.have.been.calledWith( - GtagCommand.EVENT, - 'add_to_cart', - undefined - ); - }); - - it('setCurrentScreen() calls gtag correctly (instance)', async () => { - await setCurrentScreen(gtagStub, fakeInitializationPromise, 'home'); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.CONFIG, - fakeMeasurementId, - { - 'screen_name': 'home', - update: true - } - ); - }); - - it('setCurrentScreen() calls gtag correctly (global)', async () => { - await setCurrentScreen(gtagStub, fakeInitializationPromise, 'home', { - global: true - }); - expect(gtagStub).to.be.calledWith(GtagCommand.SET, { - 'screen_name': 'home' - }); - }); - - it('setUserId() calls gtag correctly (instance)', async () => { - await setUserId(gtagStub, fakeInitializationPromise, 'user123'); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.CONFIG, - fakeMeasurementId, - { - 'user_id': 'user123', - update: true - } - ); - }); - - it('setUserId() calls gtag correctly (global)', async () => { - await setUserId(gtagStub, fakeInitializationPromise, 'user123', { - global: true - }); - expect(gtagStub).to.be.calledWith(GtagCommand.SET, { - 'user_id': 'user123' - }); - }); - - it('setUserProperties() calls gtag correctly (instance)', async () => { - await setUserProperties(gtagStub, fakeInitializationPromise, { - 'currency': 'USD', - 'language': 'en' - }); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.CONFIG, - fakeMeasurementId, - { - 'user_properties': { - 'currency': 'USD', - 'language': 'en' - }, - update: true - } - ); - }); - - it('setUserProperties() calls gtag correctly (global)', async () => { - await setUserProperties( - gtagStub, - fakeInitializationPromise, - { 'currency': 'USD', 'language': 'en' }, - { global: true } - ); - expect(gtagStub).to.be.calledWith(GtagCommand.SET, { - 'user_properties.currency': 'USD', - 'user_properties.language': 'en' - }); - }); - - it('setAnalyticsCollectionEnabled() calls gtag correctly', async () => { - await setAnalyticsCollectionEnabled(fakeInitializationPromise, true); - expect(window[`ga-disable-${fakeMeasurementId}`]).to.be.false; - await setAnalyticsCollectionEnabled(fakeInitializationPromise, false); - expect(window[`ga-disable-${fakeMeasurementId}`]).to.be.true; - delete window[`ga-disable-${fakeMeasurementId}`]; - }); -}); diff --git a/packages-exp/analytics-exp/src/functions.ts b/packages-exp/analytics-exp/src/functions.ts deleted file mode 100644 index 15c397f5799..00000000000 --- a/packages-exp/analytics-exp/src/functions.ts +++ /dev/null @@ -1,141 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AnalyticsCallOptions, - CustomParams, - ControlParams, - EventParams -} from './public-types'; -import { Gtag } from './types'; -import { GtagCommand } from './constants'; -/** - * Logs an analytics event through the Firebase SDK. - * - * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event - * @param eventName Google Analytics event name, choose from standard list or use a custom string. - * @param eventParams Analytics event parameters. - */ -export async function logEvent( - gtagFunction: Gtag, - initializationPromise: Promise, - eventName: string, - eventParams?: EventParams, - options?: AnalyticsCallOptions -): Promise { - if (options && options.global) { - gtagFunction(GtagCommand.EVENT, eventName, eventParams); - return; - } else { - const measurementId = await initializationPromise; - const params: EventParams | ControlParams = { - ...eventParams, - 'send_to': measurementId - }; - gtagFunction(GtagCommand.EVENT, eventName, params); - } -} - -/** - * Set screen_name parameter for this Google Analytics ID. - * - * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event - * @param screenName Screen name string to set. - */ -export async function setCurrentScreen( - gtagFunction: Gtag, - initializationPromise: Promise, - screenName: string | null, - options?: AnalyticsCallOptions -): Promise { - if (options && options.global) { - gtagFunction(GtagCommand.SET, { 'screen_name': screenName }); - return Promise.resolve(); - } else { - const measurementId = await initializationPromise; - gtagFunction(GtagCommand.CONFIG, measurementId, { - update: true, - 'screen_name': screenName - }); - } -} - -/** - * Set user_id parameter for this Google Analytics ID. - * - * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event - * @param id User ID string to set - */ -export async function setUserId( - gtagFunction: Gtag, - initializationPromise: Promise, - id: string | null, - options?: AnalyticsCallOptions -): Promise { - if (options && options.global) { - gtagFunction(GtagCommand.SET, { 'user_id': id }); - return Promise.resolve(); - } else { - const measurementId = await initializationPromise; - gtagFunction(GtagCommand.CONFIG, measurementId, { - update: true, - 'user_id': id - }); - } -} - -/** - * Set all other user properties other than user_id and screen_name. - * - * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event - * @param properties Map of user properties to set - */ -export async function setUserProperties( - gtagFunction: Gtag, - initializationPromise: Promise, - properties: CustomParams, - options?: AnalyticsCallOptions -): Promise { - if (options && options.global) { - const flatProperties: { [key: string]: unknown } = {}; - for (const key of Object.keys(properties)) { - // use dot notation for merge behavior in gtag.js - flatProperties[`user_properties.${key}`] = properties[key]; - } - gtagFunction(GtagCommand.SET, flatProperties); - return Promise.resolve(); - } else { - const measurementId = await initializationPromise; - gtagFunction(GtagCommand.CONFIG, measurementId, { - update: true, - 'user_properties': properties - }); - } -} - -/** - * Set whether collection is enabled for this ID. - * - * @param enabled If true, collection is enabled for this ID. - */ -export async function setAnalyticsCollectionEnabled( - initializationPromise: Promise, - enabled: boolean -): Promise { - const measurementId = await initializationPromise; - window[`ga-disable-${measurementId}`] = !enabled; -} diff --git a/packages-exp/analytics-exp/src/get-config.test.ts b/packages-exp/analytics-exp/src/get-config.test.ts deleted file mode 100644 index 84aafaa412d..00000000000 --- a/packages-exp/analytics-exp/src/get-config.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonStub, stub, useFakeTimers, restore } from 'sinon'; -import '../testing/setup'; -import { - fetchDynamicConfig, - fetchDynamicConfigWithRetry, - AppFields, - LONG_RETRY_FACTOR -} from './get-config'; -import { DYNAMIC_CONFIG_URL } from './constants'; -import { getFakeApp } from '../testing/get-fake-firebase-services'; -import { DynamicConfig, MinimalDynamicConfig } from './types'; -import { AnalyticsError } from './errors'; - -const fakeMeasurementId = 'abcd-efgh-ijkl'; -const fakeAppId = 'abcdefgh12345:23405'; -const fakeAppParams = { appId: fakeAppId, apiKey: 'AAbbCCdd12345' }; -const fakeUrl = DYNAMIC_CONFIG_URL.replace('{app-id}', fakeAppId); -const successObject = { measurementId: fakeMeasurementId, appId: fakeAppId }; -let fetchStub: SinonStub; - -function stubFetch(status: number, body: { [key: string]: any }): void { - fetchStub = stub(window, 'fetch'); - const mockResponse = new window.Response(JSON.stringify(body), { - status - }); - fetchStub.returns(Promise.resolve(mockResponse)); -} - -describe('Dynamic Config Fetch Functions', () => { - afterEach(restore); - describe('fetchDynamicConfig() - no retry', () => { - it('successfully request and receives dynamic config JSON data', async () => { - stubFetch(200, successObject); - const config: DynamicConfig = await fetchDynamicConfig(fakeAppParams); - expect(fetchStub.args[0][0]).to.equal(fakeUrl); - expect(fetchStub.args[0][1].headers.get('x-goog-api-key')).to.equal( - fakeAppParams.apiKey - ); - expect(config.appId).to.equal(fakeAppId); - expect(config.measurementId).to.equal(fakeMeasurementId); - }); - it('throws error on failed response', async () => { - stubFetch(500, { - error: { - /* no message */ - } - }); - const app = getFakeApp(fakeAppParams); - await expect( - fetchDynamicConfig(app.options as AppFields) - ).to.be.rejectedWith(AnalyticsError.CONFIG_FETCH_FAILED); - }); - it('throws error on failed response, includes server error message if provided', async () => { - stubFetch(500, { error: { message: 'Oops' } }); - const app = getFakeApp(fakeAppParams); - await expect( - fetchDynamicConfig(app.options as AppFields) - ).to.be.rejectedWith( - new RegExp(`Oops.+${AnalyticsError.CONFIG_FETCH_FAILED}`) - ); - }); - }); - describe('fetchDynamicConfigWithRetry()', () => { - it('successfully request and receives dynamic config JSON data', async () => { - stubFetch(200, successObject); - const app = getFakeApp(fakeAppParams); - const config: - | DynamicConfig - | MinimalDynamicConfig = await fetchDynamicConfigWithRetry(app); - expect(fetchStub.args[0][0]).to.equal(fakeUrl); - expect(fetchStub.args[0][1].headers.get('x-goog-api-key')).to.equal( - fakeAppParams.apiKey - ); - expect(config.appId).to.equal(fakeAppId); - expect(config.measurementId).to.equal(fakeMeasurementId); - }); - it('throws error on non-retriable failed response', async () => { - stubFetch(404, { - error: { - /* no message */ - } - }); - const app = getFakeApp(fakeAppParams); - await expect(fetchDynamicConfigWithRetry(app)).to.be.rejectedWith( - AnalyticsError.CONFIG_FETCH_FAILED - ); - }); - it('warns on non-retriable failed response if local measurementId available', async () => { - stubFetch(404, { - error: { - /* no message */ - } - }); - const consoleStub = stub(console, 'warn'); - const app = getFakeApp({ - ...fakeAppParams, - measurementId: fakeMeasurementId - }); - await fetchDynamicConfigWithRetry(app); - expect(consoleStub.args[0][1]).to.include(fakeMeasurementId); - consoleStub.restore(); - }); - it('retries on retriable error until success', async () => { - // Configures Date.now() to advance clock from zero in 20ms increments, enabling - // tests to assert a known throttle end time and allow setTimeout to work. - const clock = useFakeTimers({ shouldAdvanceTime: true }); - - // Ensures backoff is always zero, which simplifies reasoning about timer. - const powSpy = stub(Math, 'pow').returns(0); - const randomSpy = stub(Math, 'random').returns(0.5); - const fakeRetryData = { - throttleMetadata: {}, - getThrottleMetadata: stub(), - setThrottleMetadata: stub(), - deleteThrottleMetadata: stub(), - intervalMillis: 5 - }; - - // Returns responses with each of 4 retriable statuses, then a success response. - const retriableStatuses = [429, 500, 503, 504]; - fetchStub = stub(window, 'fetch'); - retriableStatuses.forEach((status, index) => { - const failResponse = new window.Response(JSON.stringify({}), { - status - }); - fetchStub.onCall(index).resolves(failResponse); - }); - const successResponse = new window.Response( - JSON.stringify(successObject), - { - status: 200 - } - ); - fetchStub.onCall(retriableStatuses.length).resolves(successResponse); - - const app = getFakeApp(fakeAppParams); - const config: - | DynamicConfig - | MinimalDynamicConfig = await fetchDynamicConfigWithRetry( - app, - fakeRetryData - ); - - // Verify retryData.setThrottleMetadata() was called on each retry. - for (let i = 0; i < retriableStatuses.length; i++) { - retriableStatuses[i]; - expect(fakeRetryData.setThrottleMetadata.args[i][1]).to.deep.equal({ - backoffCount: i + 1, - throttleEndTimeMillis: (i + 1) * 20 - }); - } - - expect(fetchStub.args[0][0]).to.equal(fakeUrl); - expect(fetchStub.args[0][1].headers.get('x-goog-api-key')).to.equal( - fakeAppParams.apiKey - ); - expect(config.appId).to.equal(fakeAppId); - expect(config.measurementId).to.equal(fakeMeasurementId); - - powSpy.restore(); - randomSpy.restore(); - clock.restore(); - }); - it('retries on retriable error until aborted by timeout', async () => { - const fakeRetryData = { - throttleMetadata: {}, - getThrottleMetadata: stub(), - setThrottleMetadata: stub(), - deleteThrottleMetadata: stub(), - intervalMillis: 10 - }; - - // Always returns retriable server error. - stubFetch(500, {}); - - const app = getFakeApp(fakeAppParams); - // Set fetch timeout to 50 ms. - const fetchPromise = fetchDynamicConfigWithRetry(app, fakeRetryData, 50); - await expect(fetchPromise).to.be.rejectedWith( - AnalyticsError.FETCH_THROTTLE - ); - // Should be enough time for at least 2 retries, including fuzzing. - expect(fakeRetryData.setThrottleMetadata.callCount).to.be.greaterThan(1); - }); - it('retries on 503 error until aborted by timeout', async () => { - const fakeRetryData = { - throttleMetadata: {}, - getThrottleMetadata: stub(), - setThrottleMetadata: stub(), - deleteThrottleMetadata: stub(), - intervalMillis: 10 - }; - - // Always returns retriable server error. - stubFetch(503, {}); - - const app = getFakeApp(fakeAppParams); - // Set fetch timeout to 50 ms. - const fetchPromise = fetchDynamicConfigWithRetry(app, fakeRetryData, 50); - await expect(fetchPromise).to.be.rejectedWith( - AnalyticsError.FETCH_THROTTLE - ); - const retryTime1 = - fakeRetryData.setThrottleMetadata.args[0][1].throttleEndTimeMillis; - const retryTime2 = - fakeRetryData.setThrottleMetadata.args[1][1].throttleEndTimeMillis; - expect(fakeRetryData.setThrottleMetadata).to.be.called; - // Interval between first and second retry should be greater than lowest fuzzable - // value of LONG_RETRY_FACTOR. - expect(retryTime2 - retryTime1).to.be.at.least( - Math.floor(LONG_RETRY_FACTOR / 2) * fakeRetryData.intervalMillis - ); - }); - it( - 'retries on retriable error until aborted by timeout,' + - ' then uses local measurementId if available', - async () => { - const fakeRetryData = { - throttleMetadata: {}, - getThrottleMetadata: stub(), - setThrottleMetadata: stub(), - deleteThrottleMetadata: stub(), - intervalMillis: 10 - }; - - // Always returns retriable server error. - stubFetch(500, {}); - const consoleStub = stub(console, 'warn'); - - const app = getFakeApp({ - ...fakeAppParams, - measurementId: fakeMeasurementId - }); - // Set fetch timeout to 50 ms. - await fetchDynamicConfigWithRetry(app, fakeRetryData, 50); - expect(consoleStub.args[0][1]).to.include(fakeMeasurementId); - consoleStub.restore(); - } - ); - }); -}); diff --git a/packages-exp/analytics-exp/src/get-config.ts b/packages-exp/analytics-exp/src/get-config.ts deleted file mode 100644 index 0b03867c857..00000000000 --- a/packages-exp/analytics-exp/src/get-config.ts +++ /dev/null @@ -1,320 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Most logic is copied from packages/remote-config/src/client/retrying_client.ts - */ - -import { FirebaseApp } from '@firebase/app-exp'; -import { DynamicConfig, ThrottleMetadata, MinimalDynamicConfig } from './types'; -import { FirebaseError, calculateBackoffMillis } from '@firebase/util'; -import { AnalyticsError, ERROR_FACTORY } from './errors'; -import { DYNAMIC_CONFIG_URL, FETCH_TIMEOUT_MILLIS } from './constants'; -import { logger } from './logger'; - -// App config fields needed by analytics. -export interface AppFields { - appId: string; - apiKey: string; - measurementId?: string; -} - -/** - * Backoff factor for 503 errors, which we want to be conservative about - * to avoid overloading servers. Each retry interval will be - * BASE_INTERVAL_MILLIS * LONG_RETRY_FACTOR ^ retryCount, so the second one - * will be ~30 seconds (with fuzzing). - */ -export const LONG_RETRY_FACTOR = 30; - -/** - * Base wait interval to multiplied by backoffFactor^backoffCount. - */ -const BASE_INTERVAL_MILLIS = 1000; - -/** - * Stubbable retry data storage class. - */ -class RetryData { - constructor( - public throttleMetadata: { [appId: string]: ThrottleMetadata } = {}, - public intervalMillis: number = BASE_INTERVAL_MILLIS - ) {} - - getThrottleMetadata(appId: string): ThrottleMetadata { - return this.throttleMetadata[appId]; - } - - setThrottleMetadata(appId: string, metadata: ThrottleMetadata): void { - this.throttleMetadata[appId] = metadata; - } - - deleteThrottleMetadata(appId: string): void { - delete this.throttleMetadata[appId]; - } -} - -const defaultRetryData = new RetryData(); - -/** - * Set GET request headers. - * @param apiKey App API key. - */ -function getHeaders(apiKey: string): Headers { - return new Headers({ - Accept: 'application/json', - 'x-goog-api-key': apiKey - }); -} - -/** - * Fetches dynamic config from backend. - * @param app Firebase app to fetch config for. - */ -export async function fetchDynamicConfig( - appFields: AppFields -): Promise { - const { appId, apiKey } = appFields; - const request: RequestInit = { - method: 'GET', - headers: getHeaders(apiKey) - }; - const appUrl = DYNAMIC_CONFIG_URL.replace('{app-id}', appId); - const response = await fetch(appUrl, request); - if (response.status !== 200 && response.status !== 304) { - let errorMessage = ''; - try { - // Try to get any error message text from server response. - const jsonResponse = (await response.json()) as { - error?: { message?: string }; - }; - if (jsonResponse.error?.message) { - errorMessage = jsonResponse.error.message; - } - } catch (_ignored) {} - throw ERROR_FACTORY.create(AnalyticsError.CONFIG_FETCH_FAILED, { - httpStatus: response.status, - responseMessage: errorMessage - }); - } - return response.json(); -} - -/** - * Fetches dynamic config from backend, retrying if failed. - * @param app Firebase app to fetch config for. - */ -export async function fetchDynamicConfigWithRetry( - app: FirebaseApp, - // retryData and timeoutMillis are parameterized to allow passing a different value for testing. - retryData: RetryData = defaultRetryData, - timeoutMillis?: number -): Promise { - const { appId, apiKey, measurementId } = app.options; - - if (!appId) { - throw ERROR_FACTORY.create(AnalyticsError.NO_APP_ID); - } - - if (!apiKey) { - if (measurementId) { - return { - measurementId, - appId - }; - } - throw ERROR_FACTORY.create(AnalyticsError.NO_API_KEY); - } - - const throttleMetadata: ThrottleMetadata = retryData.getThrottleMetadata( - appId - ) || { - backoffCount: 0, - throttleEndTimeMillis: Date.now() - }; - - const signal = new AnalyticsAbortSignal(); - - setTimeout( - async () => { - // Note a very low delay, eg < 10ms, can elapse before listeners are initialized. - signal.abort(); - }, - timeoutMillis !== undefined ? timeoutMillis : FETCH_TIMEOUT_MILLIS - ); - - return attemptFetchDynamicConfigWithRetry( - { appId, apiKey, measurementId }, - throttleMetadata, - signal, - retryData - ); -} - -/** - * Runs one retry attempt. - * @param appFields Necessary app config fields. - * @param throttleMetadata Ongoing metadata to determine throttling times. - * @param signal Abort signal. - */ -async function attemptFetchDynamicConfigWithRetry( - appFields: AppFields, - { throttleEndTimeMillis, backoffCount }: ThrottleMetadata, - signal: AnalyticsAbortSignal, - retryData: RetryData = defaultRetryData // for testing -): Promise { - const { appId, measurementId } = appFields; - // Starts with a (potentially zero) timeout to support resumption from stored state. - // Ensures the throttle end time is honored if the last attempt timed out. - // Note the SDK will never make a request if the fetch timeout expires at this point. - try { - await setAbortableTimeout(signal, throttleEndTimeMillis); - } catch (e) { - if (measurementId) { - logger.warn( - `Timed out fetching this Firebase app's measurement ID from the server.` + - ` Falling back to the measurement ID ${measurementId}` + - ` provided in the "measurementId" field in the local Firebase config. [${e.message}]` - ); - return { appId, measurementId }; - } - throw e; - } - - try { - const response = await fetchDynamicConfig(appFields); - - // Note the SDK only clears throttle state if response is success or non-retriable. - retryData.deleteThrottleMetadata(appId); - - return response; - } catch (e) { - if (!isRetriableError(e)) { - retryData.deleteThrottleMetadata(appId); - if (measurementId) { - logger.warn( - `Failed to fetch this Firebase app's measurement ID from the server.` + - ` Falling back to the measurement ID ${measurementId}` + - ` provided in the "measurementId" field in the local Firebase config. [${e.message}]` - ); - return { appId, measurementId }; - } else { - throw e; - } - } - - const backoffMillis = - Number(e.customData.httpStatus) === 503 - ? calculateBackoffMillis( - backoffCount, - retryData.intervalMillis, - LONG_RETRY_FACTOR - ) - : calculateBackoffMillis(backoffCount, retryData.intervalMillis); - - // Increments backoff state. - const throttleMetadata = { - throttleEndTimeMillis: Date.now() + backoffMillis, - backoffCount: backoffCount + 1 - }; - - // Persists state. - retryData.setThrottleMetadata(appId, throttleMetadata); - logger.debug(`Calling attemptFetch again in ${backoffMillis} millis`); - - return attemptFetchDynamicConfigWithRetry( - appFields, - throttleMetadata, - signal, - retryData - ); - } -} - -/** - * Supports waiting on a backoff by: - * - *
    - *
  • Promisifying setTimeout, so we can set a timeout in our Promise chain
  • - *
  • Listening on a signal bus for abort events, just like the Fetch API
  • - *
  • Failing in the same way the Fetch API fails, so timing out a live request and a throttled - * request appear the same.
  • - *
- * - *

Visible for testing. - */ -function setAbortableTimeout( - signal: AnalyticsAbortSignal, - throttleEndTimeMillis: number -): Promise { - return new Promise((resolve, reject) => { - // Derives backoff from given end time, normalizing negative numbers to zero. - const backoffMillis = Math.max(throttleEndTimeMillis - Date.now(), 0); - - const timeout = setTimeout(resolve, backoffMillis); - - // Adds listener, rather than sets onabort, because signal is a shared object. - signal.addEventListener(() => { - clearTimeout(timeout); - // If the request completes before this timeout, the rejection has no effect. - reject( - ERROR_FACTORY.create(AnalyticsError.FETCH_THROTTLE, { - throttleEndTimeMillis - }) - ); - }); - }); -} - -type RetriableError = FirebaseError & { customData: { httpStatus: string } }; - -/** - * Returns true if the {@link Error} indicates a fetch request may succeed later. - */ -function isRetriableError(e: Error): e is RetriableError { - if (!(e instanceof FirebaseError) || !e.customData) { - return false; - } - - // Uses string index defined by ErrorData, which FirebaseError implements. - const httpStatus = Number(e.customData['httpStatus']); - - return ( - httpStatus === 429 || - httpStatus === 500 || - httpStatus === 503 || - httpStatus === 504 - ); -} - -/** - * Shims a minimal AbortSignal (copied from Remote Config). - * - *

AbortController's AbortSignal conveniently decouples fetch timeout logic from other aspects - * of networking, such as retries. Firebase doesn't use AbortController enough to justify a - * polyfill recommendation, like we do with the Fetch API, but this minimal shim can easily be - * swapped out if/when we do. - */ -export class AnalyticsAbortSignal { - listeners: Array<() => void> = []; - addEventListener(listener: () => void): void { - this.listeners.push(listener); - } - abort(): void { - this.listeners.forEach(listener => listener()); - } -} diff --git a/packages-exp/analytics-exp/src/helpers.test.ts b/packages-exp/analytics-exp/src/helpers.test.ts deleted file mode 100644 index 79614f9edf4..00000000000 --- a/packages-exp/analytics-exp/src/helpers.test.ts +++ /dev/null @@ -1,499 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import '../testing/setup'; -import { DataLayer, Gtag, DynamicConfig } from './types'; -import { - getOrCreateDataLayer, - insertScriptTag, - wrapOrCreateGtag, - findGtagScriptOnPage, - promiseAllSettled -} from './helpers'; -import { GtagCommand } from './constants'; -import { Deferred } from '@firebase/util'; - -const fakeMeasurementId = 'abcd-efgh-ijkl'; -const fakeAppId = 'my-test-app-1234'; -const fakeDynamicConfig: DynamicConfig = { - projectId: '---', - appId: fakeAppId, - databaseURL: '---', - storageBucket: '---', - locationId: '---', - apiKey: '---', - authDomain: '---', - messagingSenderId: '---', - measurementId: fakeMeasurementId -}; -const fakeDynamicConfigPromises = [Promise.resolve(fakeDynamicConfig)]; - -describe('Gtag wrapping functions', () => { - it('getOrCreateDataLayer is able to create a new data layer if none exists', () => { - delete window['dataLayer']; - expect(getOrCreateDataLayer('dataLayer')).to.deep.equal([]); - }); - - it('getOrCreateDataLayer is able to correctly identify an existing data layer', () => { - const existingDataLayer = (window['dataLayer'] = []); - expect(getOrCreateDataLayer('dataLayer')).to.equal(existingDataLayer); - }); - - it('insertScriptIfNeeded inserts script tag', () => { - expect(findGtagScriptOnPage()).to.be.null; - insertScriptTag('customDataLayerName', fakeMeasurementId); - const scriptTag = findGtagScriptOnPage(); - expect(scriptTag).to.not.be.null; - expect(scriptTag!.src).to.contain(`l=customDataLayerName`); - expect(scriptTag!.src).to.contain(`id=${fakeMeasurementId}`); - }); - - describe('wrapOrCreateGtag() when user has not previously inserted a gtag script tag on this page', () => { - afterEach(() => { - delete window['gtag']; - delete window['dataLayer']; - }); - - it('wrapOrCreateGtag creates new gtag function if needed', () => { - expect(window['gtag']).to.not.exist; - wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); - expect(window['gtag']).to.exist; - }); - - it('new window.gtag function waits for all initialization promises before sending group events', async () => { - const initPromise1 = new Deferred(); - const initPromise2 = new Deferred(); - wrapOrCreateGtag( - { - [fakeAppId]: initPromise1.promise, - otherId: initPromise2.promise - }, - fakeDynamicConfigPromises, - {}, - 'dataLayer', - 'gtag' - ); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123', - 'send_to': 'some_group' - }); - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - initPromise1.resolve(fakeMeasurementId); // Resolves first initialization promise. - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - initPromise2.resolve('other-measurement-id'); // Resolves second initialization promise. - await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - await promiseAllSettled(fakeDynamicConfigPromises); - - expect((window['dataLayer'] as DataLayer).length).to.equal(1); - }); - - it( - 'new window.gtag function waits for all initialization promises before sending ' + - 'event with at least one unknown send_to ID', - async () => { - const initPromise1 = new Deferred(); - const initPromise2 = new Deferred(); - wrapOrCreateGtag( - { - [fakeAppId]: initPromise1.promise, - otherId: initPromise2.promise - }, - fakeDynamicConfigPromises, - { [fakeMeasurementId]: fakeAppId }, - 'dataLayer', - 'gtag' - ); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123', - 'send_to': [fakeMeasurementId, 'some_group'] - }); - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - initPromise1.resolve(); // Resolves first initialization promise. - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - initPromise2.resolve(); // Resolves second initialization promise. - await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - await promiseAllSettled(fakeDynamicConfigPromises); - - expect((window['dataLayer'] as DataLayer).length).to.equal(1); - } - ); - - it( - 'new window.gtag function waits for all initialization promises before sending ' + - 'events with no send_to field', - async () => { - const initPromise1 = new Deferred(); - const initPromise2 = new Deferred(); - wrapOrCreateGtag( - { - [fakeAppId]: initPromise1.promise, - otherId: initPromise2.promise - }, - fakeDynamicConfigPromises, - { [fakeMeasurementId]: fakeAppId }, - 'dataLayer', - 'gtag' - ); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123' - }); - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - initPromise1.resolve(); // Resolves first initialization promise. - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - initPromise2.resolve(); // Resolves second initialization promise. - await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - - expect((window['dataLayer'] as DataLayer).length).to.equal(1); - } - ); - - it( - 'new window.gtag function only waits for firebase initialization promise ' + - 'before sending event only targeted to Firebase instance GA ID', - async () => { - const initPromise1 = new Deferred(); - const initPromise2 = new Deferred(); - wrapOrCreateGtag( - { - [fakeAppId]: initPromise1.promise, - otherId: initPromise2.promise - }, - fakeDynamicConfigPromises, - { [fakeMeasurementId]: fakeAppId }, - 'dataLayer', - 'gtag' - ); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123', - 'send_to': fakeMeasurementId - }); - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - initPromise1.resolve(); // Resolves first initialization promise. - await promiseAllSettled(fakeDynamicConfigPromises); - await Promise.all([initPromise1]); // Wait for resolution of Promise.all() - - expect((window['dataLayer'] as DataLayer).length).to.equal(1); - } - ); - - it('new window.gtag function does not wait before sending events if there are no pending initialization promises', async () => { - wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123' - }); - await Promise.all([]); // Promise.all() always runs before event call, even if empty. - expect((window['dataLayer'] as DataLayer).length).to.equal(1); - }); - - it('new window.gtag function does not wait when sending "set" calls', async () => { - wrapOrCreateGtag( - { [fakeAppId]: Promise.resolve(fakeMeasurementId) }, - fakeDynamicConfigPromises, - {}, - 'dataLayer', - 'gtag' - ); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.SET, { 'language': 'en' }); - expect((window['dataLayer'] as DataLayer).length).to.equal(1); - }); - - it('new window.gtag function waits for initialization promise when sending "config" calls', async () => { - const initPromise1 = new Deferred(); - wrapOrCreateGtag( - { [fakeAppId]: initPromise1.promise }, - fakeDynamicConfigPromises, - {}, - 'dataLayer', - 'gtag' - ); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { - 'language': 'en' - }); - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - initPromise1.resolve(fakeMeasurementId); - await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. - expect((window['dataLayer'] as DataLayer).length).to.equal(0); - - await Promise.all([initPromise1]); // Wait for resolution of Promise.all() - - expect((window['dataLayer'] as DataLayer).length).to.equal(1); - }); - - it('new window.gtag function does not wait when sending "config" calls if there are no pending initialization promises', async () => { - wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { - 'transaction_id': 'abcd123' - }); - await promiseAllSettled(fakeDynamicConfigPromises); - await Promise.resolve(); // Config call is always chained onto initialization promise list, even if empty. - expect((window['dataLayer'] as DataLayer).length).to.equal(1); - }); - }); - - describe('wrapOrCreateGtag() when user has previously inserted gtag script tag on this page', () => { - const existingGtagStub: SinonStub = stub(); - - beforeEach(() => { - window['gtag'] = existingGtagStub; - }); - - afterEach(() => { - existingGtagStub.reset(); - }); - - it('new window.gtag function waits for all initialization promises before sending group events', async () => { - const initPromise1 = new Deferred(); - const initPromise2 = new Deferred(); - wrapOrCreateGtag( - { - [fakeAppId]: initPromise1.promise, - otherId: initPromise2.promise - }, - fakeDynamicConfigPromises, - { [fakeMeasurementId]: fakeAppId }, - 'dataLayer', - 'gtag' - ); - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123', - 'send_to': 'some_group' - }); - expect(existingGtagStub).to.not.be.called; - - initPromise1.resolve(); // Resolves first initialization promise. - expect(existingGtagStub).to.not.be.called; - - initPromise2.resolve(); // Resolves second initialization promise. - await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. - expect(existingGtagStub).to.not.be.called; - - await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - - expect(existingGtagStub).to.be.calledWith(GtagCommand.EVENT, 'purchase', { - 'send_to': 'some_group', - 'transaction_id': 'abcd123' - }); - }); - - it( - 'new window.gtag function waits for all initialization promises before sending ' + - 'event with at least one unknown send_to ID', - async () => { - const initPromise1 = new Deferred(); - const initPromise2 = new Deferred(); - wrapOrCreateGtag( - { - [fakeAppId]: initPromise1.promise, - otherId: initPromise2.promise - }, - fakeDynamicConfigPromises, - { [fakeMeasurementId]: fakeAppId }, - 'dataLayer', - 'gtag' - ); - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123', - 'send_to': [fakeMeasurementId, 'some_group'] - }); - expect(existingGtagStub).to.not.be.called; - - initPromise1.resolve(); // Resolves first initialization promise. - expect(existingGtagStub).to.not.be.called; - - initPromise2.resolve(); // Resolves second initialization promise. - await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. - expect(existingGtagStub).to.not.be.called; - - await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - - expect(existingGtagStub).to.be.calledWith( - GtagCommand.EVENT, - 'purchase', - { - 'send_to': [fakeMeasurementId, 'some_group'], - 'transaction_id': 'abcd123' - } - ); - } - ); - - it( - 'new window.gtag function waits for all initialization promises before sending ' + - 'events with no send_to field', - async () => { - const initPromise1 = new Deferred(); - const initPromise2 = new Deferred(); - wrapOrCreateGtag( - { - [fakeAppId]: initPromise1.promise, - otherId: initPromise2.promise - }, - fakeDynamicConfigPromises, - { [fakeMeasurementId]: fakeAppId }, - 'dataLayer', - 'gtag' - ); - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123' - }); - expect(existingGtagStub).to.not.be.called; - - initPromise1.resolve(); // Resolves first initialization promise. - expect(existingGtagStub).to.not.be.called; - - initPromise2.resolve(); // Resolves second initialization promise. - - await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - - expect(existingGtagStub).to.be.calledWith( - GtagCommand.EVENT, - 'purchase', - { 'transaction_id': 'abcd123' } - ); - } - ); - - it( - 'new window.gtag function only waits for firebase initialization promise ' + - 'before sending event only targeted to Firebase instance GA ID', - async () => { - const initPromise1 = new Deferred(); - const initPromise2 = new Deferred(); - wrapOrCreateGtag( - { - [fakeAppId]: initPromise1.promise, - otherId: initPromise2.promise - }, - fakeDynamicConfigPromises, - { [fakeMeasurementId]: fakeAppId }, - 'dataLayer', - 'gtag' - ); - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd123', - 'send_to': fakeMeasurementId - }); - expect(existingGtagStub).to.not.be.called; - - initPromise1.resolve(); // Resolves first initialization promise. - await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. - expect(existingGtagStub).to.not.be.called; - - await Promise.all([initPromise1]); // Wait for resolution of Promise.all() - - expect(existingGtagStub).to.be.calledWith( - GtagCommand.EVENT, - 'purchase', - { 'send_to': fakeMeasurementId, 'transaction_id': 'abcd123' } - ); - } - ); - - it('wrapped window.gtag function does not wait if there are no pending initialization promises', async () => { - wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd321' - }); - await Promise.all([]); // Promise.all() always runs before event call, even if empty. - expect(existingGtagStub).to.be.calledWith(GtagCommand.EVENT, 'purchase', { - 'transaction_id': 'abcd321' - }); - }); - - it('wrapped window.gtag function does not wait when sending "set" calls', async () => { - wrapOrCreateGtag( - { [fakeAppId]: Promise.resolve(fakeMeasurementId) }, - fakeDynamicConfigPromises, - {}, - 'dataLayer', - 'gtag' - ); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.SET, { 'language': 'en' }); - expect(existingGtagStub).to.be.calledWith(GtagCommand.SET, { - 'language': 'en' - }); - }); - - it('new window.gtag function waits for initialization promise when sending "config" calls', async () => { - const initPromise1 = new Deferred(); - wrapOrCreateGtag( - { [fakeAppId]: initPromise1.promise }, - fakeDynamicConfigPromises, - {}, - 'dataLayer', - 'gtag' - ); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { - 'language': 'en' - }); - expect(existingGtagStub).to.not.be.called; - - initPromise1.resolve(fakeMeasurementId); - await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. - expect(existingGtagStub).to.not.be.called; - - await Promise.all([initPromise1]); // Wait for resolution of Promise.all() - - expect(existingGtagStub).to.be.calledWith( - GtagCommand.CONFIG, - fakeMeasurementId, - { - 'language': 'en' - } - ); - }); - - it('new window.gtag function does not wait when sending "config" calls if there are no pending initialization promises', async () => { - wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); - window['dataLayer'] = []; - (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { - 'transaction_id': 'abcd123' - }); - await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. - expect(existingGtagStub).to.not.be.called; - await Promise.resolve(); // Config call is always chained onto initialization promise list, even if empty. - expect(existingGtagStub).to.be.calledWith( - GtagCommand.CONFIG, - fakeMeasurementId, - { - 'transaction_id': 'abcd123' - } - ); - }); - }); -}); diff --git a/packages-exp/analytics-exp/src/helpers.ts b/packages-exp/analytics-exp/src/helpers.ts deleted file mode 100644 index d8171f9c0f5..00000000000 --- a/packages-exp/analytics-exp/src/helpers.ts +++ /dev/null @@ -1,320 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CustomParams, ControlParams, EventParams } from './public-types'; -import { DynamicConfig, DataLayer, Gtag, MinimalDynamicConfig } from './types'; -import { GtagCommand, GTAG_URL } from './constants'; -import { logger } from './logger'; - -/** - * Makeshift polyfill for Promise.allSettled(). Resolves when all promises - * have either resolved or rejected. - * - * @param promises Array of promises to wait for. - */ -export function promiseAllSettled( - promises: Array> -): Promise { - return Promise.all(promises.map(promise => promise.catch(e => e))); -} - -/** - * Inserts gtag script tag into the page to asynchronously download gtag. - * @param dataLayerName Name of datalayer (most often the default, "_dataLayer"). - */ -export function insertScriptTag( - dataLayerName: string, - measurementId: string -): void { - const script = document.createElement('script'); - // We are not providing an analyticsId in the URL because it would trigger a `page_view` - // without fid. We will initialize ga-id using gtag (config) command together with fid. - script.src = `${GTAG_URL}?l=${dataLayerName}&id=${measurementId}`; - script.async = true; - document.head.appendChild(script); -} - -/** - * Get reference to, or create, global datalayer. - * @param dataLayerName Name of datalayer (most often the default, "_dataLayer"). - */ -export function getOrCreateDataLayer(dataLayerName: string): DataLayer { - // Check for existing dataLayer and create if needed. - let dataLayer: DataLayer = []; - if (Array.isArray(window[dataLayerName])) { - dataLayer = window[dataLayerName] as DataLayer; - } else { - window[dataLayerName] = dataLayer; - } - return dataLayer; -} - -/** - * Wrapped gtag logic when gtag is called with 'config' command. - * - * @param gtagCore Basic gtag function that just appends to dataLayer. - * @param initializationPromisesMap Map of appIds to their initialization promises. - * @param dynamicConfigPromisesList Array of dynamic config fetch promises. - * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. - * @param measurementId GA Measurement ID to set config for. - * @param gtagParams Gtag config params to set. - */ -async function gtagOnConfig( - gtagCore: Gtag, - initializationPromisesMap: { [appId: string]: Promise }, - dynamicConfigPromisesList: Array< - Promise - >, - measurementIdToAppId: { [measurementId: string]: string }, - measurementId: string, - gtagParams?: ControlParams & EventParams & CustomParams -): Promise { - // If config is already fetched, we know the appId and can use it to look up what FID promise we - /// are waiting for, and wait only on that one. - const correspondingAppId = measurementIdToAppId[measurementId as string]; - try { - if (correspondingAppId) { - await initializationPromisesMap[correspondingAppId]; - } else { - // If config is not fetched yet, wait for all configs (we don't know which one we need) and - // find the appId (if any) corresponding to this measurementId. If there is one, wait on - // that appId's initialization promise. If there is none, promise resolves and gtag - // call goes through. - const dynamicConfigResults = await promiseAllSettled( - dynamicConfigPromisesList - ); - const foundConfig = dynamicConfigResults.find( - config => config.measurementId === measurementId - ); - if (foundConfig) { - await initializationPromisesMap[foundConfig.appId]; - } - } - } catch (e) { - logger.error(e); - } - gtagCore(GtagCommand.CONFIG, measurementId, gtagParams); -} - -/** - * Wrapped gtag logic when gtag is called with 'event' command. - * - * @param gtagCore Basic gtag function that just appends to dataLayer. - * @param initializationPromisesMap Map of appIds to their initialization promises. - * @param dynamicConfigPromisesList Array of dynamic config fetch promises. - * @param measurementId GA Measurement ID to log event to. - * @param gtagParams Params to log with this event. - */ -async function gtagOnEvent( - gtagCore: Gtag, - initializationPromisesMap: { [appId: string]: Promise }, - dynamicConfigPromisesList: Array< - Promise - >, - measurementId: string, - gtagParams?: ControlParams & EventParams & CustomParams -): Promise { - try { - let initializationPromisesToWaitFor: Array> = []; - - // If there's a 'send_to' param, check if any ID specified matches - // an initializeIds() promise we are waiting for. - if (gtagParams && gtagParams['send_to']) { - let gaSendToList: string | string[] = gtagParams['send_to']; - // Make it an array if is isn't, so it can be dealt with the same way. - if (!Array.isArray(gaSendToList)) { - gaSendToList = [gaSendToList]; - } - // Checking 'send_to' fields requires having all measurement ID results back from - // the dynamic config fetch. - const dynamicConfigResults = await promiseAllSettled( - dynamicConfigPromisesList - ); - for (const sendToId of gaSendToList) { - // Any fetched dynamic measurement ID that matches this 'send_to' ID - const foundConfig = dynamicConfigResults.find( - config => config.measurementId === sendToId - ); - const initializationPromise = - foundConfig && initializationPromisesMap[foundConfig.appId]; - if (initializationPromise) { - initializationPromisesToWaitFor.push(initializationPromise); - } else { - // Found an item in 'send_to' that is not associated - // directly with an FID, possibly a group. Empty this array, - // exit the loop early, and let it get populated below. - initializationPromisesToWaitFor = []; - break; - } - } - } - - // This will be unpopulated if there was no 'send_to' field , or - // if not all entries in the 'send_to' field could be mapped to - // a FID. In these cases, wait on all pending initialization promises. - if (initializationPromisesToWaitFor.length === 0) { - initializationPromisesToWaitFor = Object.values( - initializationPromisesMap - ); - } - - // Run core gtag function with args after all relevant initialization - // promises have been resolved. - await Promise.all(initializationPromisesToWaitFor); - // Workaround for http://b/141370449 - third argument cannot be undefined. - gtagCore(GtagCommand.EVENT, measurementId, gtagParams || {}); - } catch (e) { - logger.error(e); - } -} - -/** - * Wraps a standard gtag function with extra code to wait for completion of - * relevant initialization promises before sending requests. - * - * @param gtagCore Basic gtag function that just appends to dataLayer. - * @param initializationPromisesMap Map of appIds to their initialization promises. - * @param dynamicConfigPromisesList Array of dynamic config fetch promises. - * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. - */ -function wrapGtag( - gtagCore: Gtag, - /** - * Allows wrapped gtag calls to wait on whichever intialization promises are required, - * depending on the contents of the gtag params' `send_to` field, if any. - */ - initializationPromisesMap: { [appId: string]: Promise }, - /** - * Wrapped gtag calls sometimes require all dynamic config fetches to have returned - * before determining what initialization promises (which include FIDs) to wait for. - */ - dynamicConfigPromisesList: Array< - Promise - >, - /** - * Wrapped gtag config calls can narrow down which initialization promise (with FID) - * to wait for if the measurementId is already fetched, by getting the corresponding appId, - * which is the key for the initialization promises map. - */ - measurementIdToAppId: { [measurementId: string]: string } -): Gtag { - /** - * Wrapper around gtag that ensures FID is sent with gtag calls. - * @param command Gtag command type. - * @param idOrNameOrParams Measurement ID if command is EVENT/CONFIG, params if command is SET. - * @param gtagParams Params if event is EVENT/CONFIG. - */ - async function gtagWrapper( - command: 'config' | 'set' | 'event', - idOrNameOrParams: string | ControlParams, - gtagParams?: ControlParams & EventParams & CustomParams - ): Promise { - try { - // If event, check that relevant initialization promises have completed. - if (command === GtagCommand.EVENT) { - // If EVENT, second arg must be measurementId. - await gtagOnEvent( - gtagCore, - initializationPromisesMap, - dynamicConfigPromisesList, - idOrNameOrParams as string, - gtagParams - ); - } else if (command === GtagCommand.CONFIG) { - // If CONFIG, second arg must be measurementId. - await gtagOnConfig( - gtagCore, - initializationPromisesMap, - dynamicConfigPromisesList, - measurementIdToAppId, - idOrNameOrParams as string, - gtagParams - ); - } else { - // If SET, second arg must be params. - gtagCore(GtagCommand.SET, idOrNameOrParams as CustomParams); - } - } catch (e) { - logger.error(e); - } - } - return gtagWrapper as Gtag; -} - -/** - * Creates global gtag function or wraps existing one if found. - * This wrapped function attaches Firebase instance ID (FID) to gtag 'config' and - * 'event' calls that belong to the GAID associated with this Firebase instance. - * - * @param initializationPromisesMap Map of appIds to their initialization promises. - * @param dynamicConfigPromisesList Array of dynamic config fetch promises. - * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. - * @param dataLayerName Name of global GA datalayer array. - * @param gtagFunctionName Name of global gtag function ("gtag" if not user-specified). - */ -export function wrapOrCreateGtag( - initializationPromisesMap: { [appId: string]: Promise }, - dynamicConfigPromisesList: Array< - Promise - >, - measurementIdToAppId: { [measurementId: string]: string }, - dataLayerName: string, - gtagFunctionName: string -): { - gtagCore: Gtag; - wrappedGtag: Gtag; -} { - // Create a basic core gtag function - let gtagCore: Gtag = function (..._args: unknown[]) { - // Must push IArguments object, not an array. - (window[dataLayerName] as DataLayer).push(arguments); - }; - - // Replace it with existing one if found - if ( - window[gtagFunctionName] && - typeof window[gtagFunctionName] === 'function' - ) { - // @ts-ignore - gtagCore = window[gtagFunctionName]; - } - - window[gtagFunctionName] = wrapGtag( - gtagCore, - initializationPromisesMap, - dynamicConfigPromisesList, - measurementIdToAppId - ); - - return { - gtagCore, - wrappedGtag: window[gtagFunctionName] as Gtag - }; -} - -/** - * Returns first script tag in DOM matching our gtag url pattern. - */ -export function findGtagScriptOnPage(): HTMLScriptElement | null { - const scriptTags = window.document.getElementsByTagName('script'); - for (const tag of Object.values(scriptTags)) { - if (tag.src && tag.src.includes(GTAG_URL)) { - return tag; - } - } - return null; -} diff --git a/packages-exp/analytics-exp/testing/get-fake-firebase-services.ts b/packages-exp/analytics-exp/testing/get-fake-firebase-services.ts deleted file mode 100644 index d4fe70c26b9..00000000000 --- a/packages-exp/analytics-exp/testing/get-fake-firebase-services.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - FirebaseApp, - initializeApp, - _registerComponent -} from '@firebase/app-exp'; -import { Component, ComponentType } from '@firebase/component'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; -import { AnalyticsService } from '../src/factory'; - -const fakeConfig = { - projectId: 'projectId', - authDomain: 'authDomain', - messagingSenderId: 'messagingSenderId', - databaseURL: 'databaseUrl', - storageBucket: 'storageBucket' -}; - -export function getFakeApp(fakeAppParams?: { - appId?: string; - apiKey?: string; - measurementId?: string; -}): FirebaseApp { - return { - name: 'appName', - options: { ...fakeConfig, ...fakeAppParams }, - automaticDataCollectionEnabled: true - }; -} - -export function getFakeInstallations( - fid: string = 'fid-1234', - // eslint-disable-next-line @typescript-eslint/ban-types - onFidResolve?: Function -): _FirebaseInstallationsInternal { - return { - getId: async () => { - onFidResolve && onFidResolve(); - return fid; - }, - getToken: async () => 'authToken' - }; -} - -export function getFullApp(fakeAppParams?: { - appId?: string; - apiKey?: string; - measurementId?: string; -}): FirebaseApp { - _registerComponent( - new Component( - 'installations-exp-internal', - () => { - return {} as _FirebaseInstallationsInternal; - }, - ComponentType.PUBLIC - ) - ); - _registerComponent( - new Component( - 'analytics-exp', - () => { - return {} as AnalyticsService; - }, - ComponentType.PUBLIC - ) - ); - const app = initializeApp({ ...fakeConfig, ...fakeAppParams }); - return app; -} diff --git a/packages-exp/analytics-exp/testing/gtag-script-util.ts b/packages-exp/analytics-exp/testing/gtag-script-util.ts deleted file mode 100644 index e26109cc0f2..00000000000 --- a/packages-exp/analytics-exp/testing/gtag-script-util.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { GTAG_URL } from '../src/constants'; - -export function removeGtagScript(): void { - const scriptTags = window.document.getElementsByTagName('script'); - for (const tag of Object.values(scriptTags)) { - if (tag.src) { - if (tag.src.includes(GTAG_URL) && tag.parentElement) { - tag.parentElement!.removeChild(tag); - } - } - } -} diff --git a/packages-exp/analytics-exp/testing/integration-tests/integration.ts b/packages-exp/analytics-exp/testing/integration-tests/integration.ts deleted file mode 100644 index 96995bccd2e..00000000000 --- a/packages-exp/analytics-exp/testing/integration-tests/integration.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { initializeApp, deleteApp, FirebaseApp } from '@firebase/app-exp'; -import '@firebase/installations-exp'; -import { getAnalytics, initializeAnalytics, logEvent } from '../../src/index'; -import '../setup'; -import { expect } from 'chai'; -import { stub } from 'sinon'; - -let config: Record; -try { - // eslint-disable-next-line @typescript-eslint/no-require-imports - config = require('../../../../config/project.json'); -} catch (e) { - throw new Error( - "Couldn't find config/project.json, make sure you ran test:setup." - ); -} - -const RETRY_INTERVAL = 1000; -const TIMEOUT_MILLIS = 20000; - -async function checkForEventCalls(retryCount = 0): Promise { - if (retryCount > TIMEOUT_MILLIS / RETRY_INTERVAL) { - return Promise.resolve([]); - } - await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL)); - const resources = performance.getEntriesByType('resource'); - performance.clearResourceTimings(); - const callsWithEvent = resources.filter( - resource => - resource.name.includes('google-analytics.com') && - resource.name.includes('en=login') - ); - if (callsWithEvent.length === 0) { - return checkForEventCalls(retryCount + 1); - } else { - return callsWithEvent; - } -} - -describe('FirebaseAnalytics Integration Smoke Tests', () => { - let app: FirebaseApp; - describe('Using getAnalytics()', () => { - afterEach(() => deleteApp(app)); - it('logEvent() sends correct network request.', async () => { - app = initializeApp(config); - logEvent(getAnalytics(app), 'login', { method: 'phone' }); - const eventCalls = await checkForEventCalls(); - expect(eventCalls.length).to.equal(1); - expect(eventCalls[0].name).to.include('method=phone'); - }); - it("Warns if measurement ID doesn't match.", done => { - const warnStub = stub(console, 'warn').callsFake(() => { - expect(warnStub.args[0][1]).to.include('does not match'); - done(); - }); - app = initializeApp({ - ...config, - measurementId: 'wrong-id' - }); - getAnalytics(app); - }); - }); - describe('Using initializeAnalytics()', () => { - it('logEvent() sends correct network request.', async () => { - app = initializeApp(config); - logEvent(initializeAnalytics(app), 'login', { method: 'email' }); - const eventCalls = await checkForEventCalls(); - expect(eventCalls.length).to.equal(1); - expect(eventCalls[0].name).to.include('method=email'); - }); - }); -}); diff --git a/packages-exp/analytics-exp/testing/setup.ts b/packages-exp/analytics-exp/testing/setup.ts deleted file mode 100644 index 3165aab4e7e..00000000000 --- a/packages-exp/analytics-exp/testing/setup.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { use } from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; -import * as sinonChai from 'sinon-chai'; - -use(chaiAsPromised); -use(sinonChai); diff --git a/packages-exp/app-check-compat/rollup.config.js b/packages-exp/app-check-compat/rollup.config.js deleted file mode 100644 index 9b89b7772af..00000000000 --- a/packages-exp/app-check-compat/rollup.config.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-check-compat/rollup.config.release.js b/packages-exp/app-check-compat/rollup.config.release.js deleted file mode 100644 index 1896d7036f5..00000000000 --- a/packages-exp/app-check-compat/rollup.config.release.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-check-compat/rollup.shared.js b/packages-exp/app-check-compat/rollup.shared.js deleted file mode 100644 index 24bbc5a28c7..00000000000 --- a/packages-exp/app-check-compat/rollup.shared.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -export const es5BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -export const es2017BuildsNoPlugin = [ - { - /** - * Browser Build - */ - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/app-check-compat/tsconfig.json b/packages-exp/app-check-compat/tsconfig.json deleted file mode 100644 index 356e7a53b8c..00000000000 --- a/packages-exp/app-check-compat/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "strict": true - }, - "exclude": ["dist/**/*"] -} diff --git a/packages-exp/app-check-exp/README.md b/packages-exp/app-check-exp/README.md deleted file mode 100644 index aed9638dbe9..00000000000 --- a/packages-exp/app-check-exp/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @firebase/app-check - -This is the Firebase App Check component of the Firebase JS SDK. - -**This package is not intended for direct usage, and should only be used via the officially supported [`firebase`](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/app-check-exp/package.json b/packages-exp/app-check-exp/package.json deleted file mode 100644 index 614a8ac9047..00000000000 --- a/packages-exp/app-check-exp/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "@firebase/app-check-exp", - "version": "0.0.900", - "private": true, - "description": "An App Check package for new firebase packages", - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.cjs.js", - "browser": "dist/index.esm2017.js", - "module": "dist/index.esm2017.js", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", - "build:deps": "lerna run --scope @firebase/app-check-exp --include-dependencies build", - "dev": "rollup -c -w", - "test": "run-p lint test:browser", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", - "test:browser": "karma start --single-run --nocache", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc", - "typings:public": "node ../../scripts/exp/use_typings.js ./dist/app-check-exp-public.d.ts" - }, - "peerDependencies": { - "@firebase/app-exp": "0.x" - }, - "dependencies": { - "@firebase/logger": "0.2.6", - "@firebase/util": "1.3.0", - "@firebase/component": "0.5.6", - "tslib": "^2.1.0" - }, - "license": "Apache-2.0", - "devDependencies": { - "@firebase/app-exp": "0.0.900", - "rollup": "2.52.2", - "@rollup/plugin-commonjs": "17.1.0", - "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "11.2.0", - "rollup-plugin-typescript2": "0.30.0", - "typescript": "4.2.2" - }, - "repository": { - "directory": "packages/app-check", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "typings": "dist/src/index.d.ts", - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - }, - "esm5": "dist/index.esm.js" -} \ No newline at end of file diff --git a/packages-exp/app-check-exp/rollup.config.js b/packages-exp/app-check-exp/rollup.config.js deleted file mode 100644 index 9b89b7772af..00000000000 --- a/packages-exp/app-check-exp/rollup.config.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-check-exp/rollup.config.release.js b/packages-exp/app-check-exp/rollup.config.release.js deleted file mode 100644 index aed49b162c9..00000000000 --- a/packages-exp/app-check-exp/rollup.config.release.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: id => id === '@firebase/installations' - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: id => id === '@firebase/installations' - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-check-exp/rollup.shared.js b/packages-exp/app-check-exp/rollup.shared.js deleted file mode 100644 index 24bbc5a28c7..00000000000 --- a/packages-exp/app-check-exp/rollup.shared.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -export const es5BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -export const es2017BuildsNoPlugin = [ - { - /** - * Browser Build - */ - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/app-check-exp/src/api.test.ts b/packages-exp/app-check-exp/src/api.test.ts deleted file mode 100644 index fc06ffc1ba9..00000000000 --- a/packages-exp/app-check-exp/src/api.test.ts +++ /dev/null @@ -1,315 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import '../test/setup'; -import { expect } from 'chai'; -import { spy, stub } from 'sinon'; -import { - setTokenAutoRefreshEnabled, - initializeAppCheck, - getToken, - onTokenChanged -} from './api'; -import { - FAKE_SITE_KEY, - getFullApp, - getFakeApp, - getFakeGreCAPTCHA, - getFakeAppCheck, - removegreCAPTCHAScriptsOnPage -} from '../test/util'; -import { clearState, getState } from './state'; -import * as reCAPTCHA from './recaptcha'; -import * as util from './util'; -import * as logger from './logger'; -import * as client from './client'; -import * as storage from './storage'; -import * as internalApi from './internal-api'; -import { deleteApp, FirebaseApp } from '@firebase/app-exp'; -import { CustomProvider, ReCaptchaV3Provider } from './providers'; -import { AppCheckService } from './factory'; -import { AppCheckToken } from './public-types'; - -describe('api', () => { - let app: FirebaseApp; - - beforeEach(() => { - app = getFullApp(); - stub(util, 'getRecaptcha').returns(getFakeGreCAPTCHA()); - }); - - afterEach(() => { - clearState(); - removegreCAPTCHAScriptsOnPage(); - return deleteApp(app); - }); - - describe('initializeAppCheck()', () => { - it('can only be called once (if given different provider classes)', () => { - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - expect(() => - initializeAppCheck(app, { - provider: new CustomProvider({ - getToken: () => Promise.resolve({ token: 'mm' } as AppCheckToken) - }) - }) - ).to.throw(/appCheck\/already-initialized/); - }); - it('can only be called once (if given different ReCaptchaV3Providers)', () => { - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - expect(() => - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY + 'X') - }) - ).to.throw(/appCheck\/already-initialized/); - }); - it('can only be called once (if given different CustomProviders)', () => { - initializeAppCheck(app, { - provider: new CustomProvider({ - getToken: () => Promise.resolve({ token: 'ff' } as AppCheckToken) - }) - }); - expect(() => - initializeAppCheck(app, { - provider: new CustomProvider({ - getToken: () => Promise.resolve({ token: 'gg' } as AppCheckToken) - }) - }) - ).to.throw(/appCheck\/already-initialized/); - }); - it('can be called multiple times (if given equivalent ReCaptchaV3Providers)', () => { - const appCheckInstance = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - expect( - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }) - ).to.equal(appCheckInstance); - }); - it('can be called multiple times (if given equivalent CustomProviders)', () => { - const appCheckInstance = initializeAppCheck(app, { - provider: new CustomProvider({ - getToken: () => Promise.resolve({ token: 'ff' } as AppCheckToken) - }) - }); - expect( - initializeAppCheck(app, { - provider: new CustomProvider({ - getToken: () => Promise.resolve({ token: 'ff' } as AppCheckToken) - }) - }) - ).to.equal(appCheckInstance); - }); - - it('initialize reCAPTCHA when a ReCaptchaV3Provider is provided', () => { - const initReCAPTCHAStub = stub(reCAPTCHA, 'initialize').returns( - Promise.resolve({} as any) - ); - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - expect(initReCAPTCHAStub).to.have.been.calledWithExactly( - app, - FAKE_SITE_KEY - ); - }); - - it('sets activated to true', () => { - expect(getState(app).activated).to.equal(false); - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - expect(getState(app).activated).to.equal(true); - }); - - it('isTokenAutoRefreshEnabled value defaults to global setting', () => { - app.automaticDataCollectionEnabled = false; - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - expect(getState(app).isTokenAutoRefreshEnabled).to.equal(false); - }); - - it('sets isTokenAutoRefreshEnabled correctly, overriding global setting', () => { - app.automaticDataCollectionEnabled = false; - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: true - }); - expect(getState(app).isTokenAutoRefreshEnabled).to.equal(true); - }); - }); - describe('setTokenAutoRefreshEnabled()', () => { - it('sets isTokenAutoRefreshEnabled correctly', () => { - const app = getFakeApp({ automaticDataCollectionEnabled: false }); - const appCheck = getFakeAppCheck(app); - setTokenAutoRefreshEnabled(appCheck, true); - expect(getState(app).isTokenAutoRefreshEnabled).to.equal(true); - }); - }); - describe('getToken()', () => { - it('getToken() calls the internal getToken() function', async () => { - const app = getFakeApp({ automaticDataCollectionEnabled: true }); - const appCheck = getFakeAppCheck(app); - const internalGetToken = stub(internalApi, 'getToken').resolves({ - token: 'a-token-string' - }); - await getToken(appCheck, true); - expect(internalGetToken).to.be.calledWith(appCheck, true); - }); - it('getToken() throws errors returned with token', async () => { - const app = getFakeApp({ automaticDataCollectionEnabled: true }); - const appCheck = getFakeAppCheck(app); - // If getToken() errors, it returns a dummy token with an error field - // instead of throwing. - stub(internalApi, 'getToken').resolves({ - token: 'a-dummy-token', - error: Error('there was an error') - }); - await expect(getToken(appCheck, true)).to.be.rejectedWith( - 'there was an error' - ); - }); - }); - describe('onTokenChanged()', () => { - it('Listeners work when using top-level parameters pattern', async () => { - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: true - }); - const fakeRecaptchaToken = 'fake-recaptcha-token'; - const fakeRecaptchaAppCheckToken = { - token: 'fake-recaptcha-app-check-token', - expireTimeMillis: 123, - issuedAtTimeMillis: 0 - }; - stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken)); - stub(client, 'exchangeToken').returns( - Promise.resolve(fakeRecaptchaAppCheckToken) - ); - stub(storage, 'writeTokenToStorage').returns(Promise.resolve(undefined)); - - const listener1 = stub().throws(new Error()); - const listener2 = spy(); - - const errorFn1 = spy(); - const errorFn2 = spy(); - - const unsubscribe1 = onTokenChanged(appCheck, listener1, errorFn1); - const unsubscribe2 = onTokenChanged(appCheck, listener2, errorFn2); - - expect(getState(app).tokenObservers.length).to.equal(2); - - await internalApi.getToken(appCheck as AppCheckService); - - expect(listener1).to.be.called; - expect(listener2).to.be.calledWith({ - token: fakeRecaptchaAppCheckToken.token - }); - // onError should not be called on listener errors. - expect(errorFn1).to.not.be.called; - expect(errorFn2).to.not.be.called; - unsubscribe1(); - unsubscribe2(); - expect(getState(app).tokenObservers.length).to.equal(0); - }); - - it('Listeners work when using Observer pattern', async () => { - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: true - }); - const fakeRecaptchaToken = 'fake-recaptcha-token'; - const fakeRecaptchaAppCheckToken = { - token: 'fake-recaptcha-app-check-token', - expireTimeMillis: 123, - issuedAtTimeMillis: 0 - }; - stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken)); - stub(client, 'exchangeToken').returns( - Promise.resolve(fakeRecaptchaAppCheckToken) - ); - stub(storage, 'writeTokenToStorage').returns(Promise.resolve(undefined)); - - const listener1 = stub().throws(new Error()); - const listener2 = spy(); - - const errorFn1 = spy(); - const errorFn2 = spy(); - - /** - * Reverse the order of adding the failed and successful handler, for extra - * testing. - */ - const unsubscribe2 = onTokenChanged(appCheck, { - next: listener2, - error: errorFn2 - }); - const unsubscribe1 = onTokenChanged(appCheck, { - next: listener1, - error: errorFn1 - }); - - expect(getState(app).tokenObservers.length).to.equal(2); - - await internalApi.getToken(appCheck as AppCheckService); - - expect(listener1).to.be.called; - expect(listener2).to.be.calledWith({ - token: fakeRecaptchaAppCheckToken.token - }); - // onError should not be called on listener errors. - expect(errorFn1).to.not.be.called; - expect(errorFn2).to.not.be.called; - unsubscribe1(); - unsubscribe2(); - expect(getState(app).tokenObservers.length).to.equal(0); - }); - - it('onError() catches token errors', async () => { - stub(logger.logger, 'error'); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: false - }); - const fakeRecaptchaToken = 'fake-recaptcha-token'; - stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken)); - stub(client, 'exchangeToken').rejects('exchange error'); - stub(storage, 'writeTokenToStorage').returns(Promise.resolve(undefined)); - - const listener1 = spy(); - - const errorFn1 = spy(); - - const unsubscribe1 = onTokenChanged(appCheck, listener1, errorFn1); - - await internalApi.getToken(appCheck as AppCheckService); - - expect(getState(app).tokenObservers.length).to.equal(1); - - expect(errorFn1).to.be.calledOnce; - expect(errorFn1.args[0][0].name).to.include('exchange error'); - - unsubscribe1(); - expect(getState(app).tokenObservers.length).to.equal(0); - }); - }); -}); diff --git a/packages-exp/app-check-exp/src/api.ts b/packages-exp/app-check-exp/src/api.ts deleted file mode 100644 index fefe64519f3..00000000000 --- a/packages-exp/app-check-exp/src/api.ts +++ /dev/null @@ -1,257 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AppCheck, - AppCheckOptions, - AppCheckTokenResult, - Unsubscribe, - PartialObserver -} from './public-types'; -import { ERROR_FACTORY, AppCheckError } from './errors'; -import { getState, setState, AppCheckState } from './state'; -import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; -import { getModularInstance, ErrorFn, NextFn } from '@firebase/util'; -import { AppCheckService } from './factory'; -import { AppCheckProvider, ListenerType } from './types'; -import { - getToken as getTokenInternal, - addTokenListener, - removeTokenListener, - isValid -} from './internal-api'; -import { readTokenFromStorage } from './storage'; - -declare module '@firebase/component' { - interface NameServiceMapping { - 'app-check-exp': AppCheckService; - } -} - -export { ReCaptchaV3Provider, CustomProvider } from './providers'; - -/** - * Activate App Check for the given app. Can be called only once per app. - * @param app - the {@link @firebase/app#FirebaseApp} to activate App Check for - * @param options - App Check initialization options - * @public - */ -export function initializeAppCheck( - app: FirebaseApp = getApp(), - options: AppCheckOptions -): AppCheck { - app = getModularInstance(app); - const provider = _getProvider(app, 'app-check-exp'); - - if (provider.isInitialized()) { - const existingInstance = provider.getImmediate(); - const initialOptions = provider.getOptions() as unknown as AppCheckOptions; - if ( - initialOptions.isTokenAutoRefreshEnabled === - options.isTokenAutoRefreshEnabled && - initialOptions.provider.isEqual(options.provider) - ) { - return existingInstance; - } else { - throw ERROR_FACTORY.create(AppCheckError.ALREADY_INITIALIZED, { - appName: app.name - }); - } - } - - const appCheck = provider.initialize({ options }); - _activate(app, options.provider, options.isTokenAutoRefreshEnabled); - - return appCheck; -} - -/** - * Activate App Check - * @param app - Firebase app to activate App Check for. - * @param provider - reCAPTCHA v3 provider or - * custom token provider. - * @param isTokenAutoRefreshEnabled - If true, the SDK automatically - * refreshes App Check tokens as needed. If undefined, defaults to the - * value of `app.automaticDataCollectionEnabled`, which defaults to - * false and can be set in the app config. - */ -function _activate( - app: FirebaseApp, - provider: AppCheckProvider, - isTokenAutoRefreshEnabled?: boolean -): void { - const state = getState(app); - - const newState: AppCheckState = { ...state, activated: true }; - newState.provider = provider; // Read cached token from storage if it exists and store it in memory. - newState.cachedTokenPromise = readTokenFromStorage(app).then(cachedToken => { - if (cachedToken && isValid(cachedToken)) { - setState(app, { ...getState(app), token: cachedToken }); - } - return cachedToken; - }); - - // Use value of global `automaticDataCollectionEnabled` (which - // itself defaults to false if not specified in config) if - // `isTokenAutoRefreshEnabled` param was not provided by user. - newState.isTokenAutoRefreshEnabled = - isTokenAutoRefreshEnabled === undefined - ? app.automaticDataCollectionEnabled - : isTokenAutoRefreshEnabled; - - setState(app, newState); - - newState.provider.initialize(app); -} - -/** - * Set whether App Check will automatically refresh tokens as needed. - * - * @param appCheckInstance - The App Check service instance. - * @param isTokenAutoRefreshEnabled - If true, the SDK automatically - * refreshes App Check tokens as needed. This overrides any value set - * during `initializeAppCheck()`. - * @public - */ -export function setTokenAutoRefreshEnabled( - appCheckInstance: AppCheck, - isTokenAutoRefreshEnabled: boolean -): void { - const app = appCheckInstance.app; - const state = getState(app); - // This will exist if any product libraries have called - // `addTokenListener()` - if (state.tokenRefresher) { - if (isTokenAutoRefreshEnabled === true) { - state.tokenRefresher.start(); - } else { - state.tokenRefresher.stop(); - } - } - setState(app, { ...state, isTokenAutoRefreshEnabled }); -} -/** - * Get the current App Check token. Attaches to the most recent - * in-flight request if one is present. Returns null if no token - * is present and no token requests are in-flight. - * - * @param appCheckInstance - The App Check service instance. - * @param forceRefresh - If true, will always try to fetch a fresh token. - * If false, will use a cached token if found in storage. - * @public - */ -export async function getToken( - appCheckInstance: AppCheck, - forceRefresh?: boolean -): Promise { - const result = await getTokenInternal( - appCheckInstance as AppCheckService, - forceRefresh - ); - if (result.error) { - throw result.error; - } - return { token: result.token }; -} - -/** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * @param appCheckInstance - The App Check service instance. - * @param observer - An object with `next`, `error`, and `complete` - * properties. `next` is called with an - * {@link AppCheckTokenResult} - * whenever the token changes. `error` is optional and is called if an - * error is thrown by the listener (the `next` function). `complete` - * is unused, as the token stream is unending. - * - * @returns A function that unsubscribes this listener. - * @public - */ -export function onTokenChanged( - appCheckInstance: AppCheck, - observer: PartialObserver -): Unsubscribe; -/** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * @param appCheckInstance - The App Check service instance. - * @param onNext - When the token changes, this function is called with aa - * {@link AppCheckTokenResult}. - * @param onError - Optional. Called if there is an error thrown by the - * listener (the `onNext` function). - * @param onCompletion - Currently unused, as the token stream is unending. - * @returns A function that unsubscribes this listener. - * @public - */ -export function onTokenChanged( - appCheckInstance: AppCheck, - onNext: (tokenResult: AppCheckTokenResult) => void, - onError?: (error: Error) => void, - onCompletion?: () => void -): Unsubscribe; -/** - * Wraps `addTokenListener`/`removeTokenListener` methods in an `Observer` - * pattern for public use. - */ -export function onTokenChanged( - appCheckInstance: AppCheck, - onNextOrObserver: - | ((tokenResult: AppCheckTokenResult) => void) - | PartialObserver, - onError?: (error: Error) => void, - /** - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the token stream is never-ending. - * It is added only for API consistency with the observer pattern, which - * we follow in JS APIs. - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onCompletion?: () => void -): Unsubscribe { - let nextFn: NextFn = () => {}; - let errorFn: ErrorFn = () => {}; - if ((onNextOrObserver as PartialObserver).next != null) { - nextFn = ( - onNextOrObserver as PartialObserver - ).next!.bind(onNextOrObserver); - } else { - nextFn = onNextOrObserver as NextFn; - } - if ( - (onNextOrObserver as PartialObserver).error != null - ) { - errorFn = ( - onNextOrObserver as PartialObserver - ).error!.bind(onNextOrObserver); - } else if (onError) { - errorFn = onError; - } - addTokenListener( - appCheckInstance as AppCheckService, - ListenerType.EXTERNAL, - nextFn, - errorFn - ); - return () => removeTokenListener(appCheckInstance.app, nextFn); -} diff --git a/packages-exp/app-check-exp/src/client.test.ts b/packages-exp/app-check-exp/src/client.test.ts deleted file mode 100644 index fbdb524c464..00000000000 --- a/packages-exp/app-check-exp/src/client.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../test/setup'; -import { expect } from 'chai'; -import { stub, SinonStub, useFakeTimers } from 'sinon'; -import { FirebaseApp } from '@firebase/app-exp'; -import { getFakeApp, getFakePlatformLoggingProvider } from '../test/util'; -import { getExchangeRecaptchaTokenRequest, exchangeToken } from './client'; -import { FirebaseError } from '@firebase/util'; -import { ERROR_FACTORY, AppCheckError } from './errors'; -import { BASE_ENDPOINT } from './constants'; - -describe('client', () => { - let app: FirebaseApp; - let fetchStub: SinonStub<[RequestInfo, RequestInit?], Promise>; - beforeEach(() => { - app = getFakeApp(); - fetchStub = stub(window, 'fetch').returns( - Promise.resolve(new Response('{}')) - ); - }); - - it('creates exchange recaptcha token request correctly', () => { - const request = getExchangeRecaptchaTokenRequest( - app, - 'fake-recaptcha-token' - ); - const { projectId, appId, apiKey } = app.options; - - expect(request).to.deep.equal({ - url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:exchangeRecaptchaToken?key=${apiKey}`, - body: { - // eslint-disable-next-line camelcase - recaptcha_token: 'fake-recaptcha-token' - } - }); - }); - - it('returns a AppCheck token', async () => { - // To get a consistent expireTime/issuedAtTime. - const clock = useFakeTimers(); - fetchStub.returns( - Promise.resolve({ - status: 200, - json: async () => ({ - attestationToken: 'fake-appcheck-token', - ttl: '3.600s' - }) - } as Response) - ); - - const response = await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), - getFakePlatformLoggingProvider('a/1.2.3 fire-app-check/2.3.4') - ); - - expect( - (fetchStub.args[0][1]?.['headers'] as any)['X-Firebase-Client'] - ).to.equal('a/1.2.3 fire-app-check/2.3.4'); - - expect(response).to.deep.equal({ - token: 'fake-appcheck-token', - expireTimeMillis: 3600, - issuedAtTimeMillis: 0 - }); - clock.restore(); - }); - - it('throws when there is a network error', async () => { - const originalError = new TypeError('Network request failed'); - fetchStub.returns(Promise.reject(originalError)); - const firebaseError = ERROR_FACTORY.create( - AppCheckError.FETCH_NETWORK_ERROR, - { - originalErrorMessage: originalError.message - } - ); - - try { - await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), - getFakePlatformLoggingProvider() - ); - } catch (e) { - expect(e).instanceOf(FirebaseError); - expect(e).has.property('message', firebaseError.message); - expect(e).has.nested.property( - 'customData.originalErrorMessage', - 'Network request failed' - ); - } - }); - - it('throws when response status is not 200', async () => { - fetchStub.returns( - Promise.resolve({ - status: 500 - } as Response) - ); - - const firebaseError = ERROR_FACTORY.create( - AppCheckError.FETCH_STATUS_ERROR, - { - httpStatus: 500 - } - ); - - try { - await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), - getFakePlatformLoggingProvider() - ); - } catch (e) { - expect(e).instanceOf(FirebaseError); - expect(e).has.property('message', firebaseError.message); - expect(e).has.nested.property('customData.httpStatus', 500); - } - }); - - it('throws if the response body is not json', async () => { - const originalError = new SyntaxError('invalid JSON string'); - fetchStub.returns( - Promise.resolve({ - status: 200, - json: () => Promise.reject(originalError) - } as Response) - ); - - const firebaseError = ERROR_FACTORY.create( - AppCheckError.FETCH_PARSE_ERROR, - { - originalErrorMessage: originalError.message - } - ); - - try { - await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), - getFakePlatformLoggingProvider() - ); - } catch (e) { - expect(e).instanceOf(FirebaseError); - expect(e).has.property('message', firebaseError.message); - expect(e).has.nested.property( - 'customData.originalErrorMessage', - originalError.message - ); - } - }); - - it('throws if timeToLive field is not a number', async () => { - fetchStub.returns( - Promise.resolve({ - status: 200, - json: () => - Promise.resolve({ - attestationToken: 'fake-appcheck-token', - ttl: 'NAN' - }) - } as Response) - ); - - const firebaseError = ERROR_FACTORY.create( - AppCheckError.FETCH_PARSE_ERROR, - { - originalErrorMessage: `ttl field (timeToLive) is not in standard Protobuf Duration format: NAN` - } - ); - - try { - await exchangeToken( - getExchangeRecaptchaTokenRequest(app, 'fake-custom-token'), - getFakePlatformLoggingProvider() - ); - } catch (e) { - expect(e).instanceOf(FirebaseError); - expect(e).has.property('message', firebaseError.message); - expect(e).has.nested.property( - 'customData.originalErrorMessage', - `ttl field (timeToLive) is not in standard Protobuf Duration format: NAN` - ); - } - }); -}); diff --git a/packages-exp/app-check-exp/src/client.ts b/packages-exp/app-check-exp/src/client.ts deleted file mode 100644 index 56d01da9012..00000000000 --- a/packages-exp/app-check-exp/src/client.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - BASE_ENDPOINT, - EXCHANGE_DEBUG_TOKEN_METHOD, - EXCHANGE_RECAPTCHA_TOKEN_METHOD -} from './constants'; -import { FirebaseApp } from '@firebase/app-exp'; -import { ERROR_FACTORY, AppCheckError } from './errors'; -import { Provider } from '@firebase/component'; -import { AppCheckTokenInternal } from './types'; - -/** - * Response JSON returned from AppCheck server endpoint. - */ -interface AppCheckResponse { - attestationToken: string; - // timeToLive - ttl: string; -} - -interface AppCheckRequest { - url: string; - body: { [key: string]: string }; -} - -export async function exchangeToken( - { url, body }: AppCheckRequest, - platformLoggerProvider: Provider<'platform-logger'> -): Promise { - const headers: HeadersInit = { - 'Content-Type': 'application/json' - }; - // If platform logger exists, add the platform info string to the header. - const platformLogger = platformLoggerProvider.getImmediate({ - optional: true - }); - if (platformLogger) { - headers['X-Firebase-Client'] = platformLogger.getPlatformInfoString(); - } - const options: RequestInit = { - method: 'POST', - body: JSON.stringify(body), - headers - }; - let response; - try { - response = await fetch(url, options); - } catch (originalError) { - throw ERROR_FACTORY.create(AppCheckError.FETCH_NETWORK_ERROR, { - originalErrorMessage: originalError.message - }); - } - - if (response.status !== 200) { - throw ERROR_FACTORY.create(AppCheckError.FETCH_STATUS_ERROR, { - httpStatus: response.status - }); - } - - let responseBody: AppCheckResponse; - try { - // JSON parsing throws SyntaxError if the response body isn't a JSON string. - responseBody = await response.json(); - } catch (originalError) { - throw ERROR_FACTORY.create(AppCheckError.FETCH_PARSE_ERROR, { - originalErrorMessage: originalError.message - }); - } - - // Protobuf duration format. - // https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/Duration - const match = responseBody.ttl.match(/^([\d.]+)(s)$/); - if (!match || !match[2] || isNaN(Number(match[1]))) { - throw ERROR_FACTORY.create(AppCheckError.FETCH_PARSE_ERROR, { - originalErrorMessage: - `ttl field (timeToLive) is not in standard Protobuf Duration ` + - `format: ${responseBody.ttl}` - }); - } - const timeToLiveAsNumber = Number(match[1]) * 1000; - - const now = Date.now(); - return { - token: responseBody.attestationToken, - expireTimeMillis: now + timeToLiveAsNumber, - issuedAtTimeMillis: now - }; -} - -export function getExchangeRecaptchaTokenRequest( - app: FirebaseApp, - reCAPTCHAToken: string -): AppCheckRequest { - const { projectId, appId, apiKey } = app.options; - - return { - url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_TOKEN_METHOD}?key=${apiKey}`, - body: { - // eslint-disable-next-line - recaptcha_token: reCAPTCHAToken - } - }; -} - -export function getExchangeDebugTokenRequest( - app: FirebaseApp, - debugToken: string -): AppCheckRequest { - const { projectId, appId, apiKey } = app.options; - - return { - url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_DEBUG_TOKEN_METHOD}?key=${apiKey}`, - body: { - // eslint-disable-next-line - debug_token: debugToken - } - }; -} diff --git a/packages-exp/app-check-exp/src/constants.ts b/packages-exp/app-check-exp/src/constants.ts deleted file mode 100644 index 56cdd623427..00000000000 --- a/packages-exp/app-check-exp/src/constants.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export const BASE_ENDPOINT = - 'https://content-firebaseappcheck.googleapis.com/v1beta'; - -export const EXCHANGE_RECAPTCHA_TOKEN_METHOD = 'exchangeRecaptchaToken'; -export const EXCHANGE_DEBUG_TOKEN_METHOD = 'exchangeDebugToken'; - -export const TOKEN_REFRESH_TIME = { - /** - * The offset time before token natural expiration to run the refresh. - * This is currently 5 minutes. - */ - OFFSET_DURATION: 5 * 60 * 1000, - /** - * This is the first retrial wait after an error. This is currently - * 30 seconds. - */ - RETRIAL_MIN_WAIT: 30 * 1000, - /** - * This is the maximum retrial wait, currently 16 minutes. - */ - RETRIAL_MAX_WAIT: 16 * 60 * 1000 -}; diff --git a/packages-exp/app-check-exp/src/debug.test.ts b/packages-exp/app-check-exp/src/debug.test.ts deleted file mode 100644 index 7dc86287b89..00000000000 --- a/packages-exp/app-check-exp/src/debug.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../test/setup'; -import { expect } from 'chai'; -import { stub } from 'sinon'; -import * as storage from './storage'; -import * as indexeddb from './indexeddb'; -import { clearState, getDebugState } from './state'; -import { initializeDebugMode } from './debug'; - -describe('debug mode', () => { - afterEach(() => { - clearState(); - // reset the global variable for debug mode - self.FIREBASE_APPCHECK_DEBUG_TOKEN = undefined; - }); - - it('enables debug mode if self.FIREBASE_APPCHECK_DEBUG_TOKEN is set to a string', async () => { - self.FIREBASE_APPCHECK_DEBUG_TOKEN = 'my-debug-token'; - initializeDebugMode(); - const debugState = getDebugState(); - - expect(debugState.enabled).to.be.true; - await expect(debugState.token?.promise).to.eventually.equal( - 'my-debug-token' - ); - }); - - it('generates a debug token if self.FIREBASE_APPCHECK_DEBUG_TOKEN is set to true', async () => { - stub(storage, 'readOrCreateDebugTokenFromStorage').returns( - Promise.resolve('my-debug-token') - ); - - self.FIREBASE_APPCHECK_DEBUG_TOKEN = true; - initializeDebugMode(); - const debugState = getDebugState(); - - expect(debugState.enabled).to.be.true; - await expect(debugState.token?.promise).to.eventually.equal( - 'my-debug-token' - ); - }); - - it('saves the generated debug token to indexedDB', async () => { - const saveToIndexedDBStub = stub( - indexeddb, - 'writeDebugTokenToIndexedDB' - ).callsFake(() => Promise.resolve()); - - self.FIREBASE_APPCHECK_DEBUG_TOKEN = true; - initializeDebugMode(); - - await getDebugState().token?.promise; - expect(saveToIndexedDBStub).to.have.been.called; - }); - - it('uses the cached debug token when it exists if self.FIREBASE_APPCHECK_DEBUG_TOKEN is set to true', async () => { - stub(indexeddb, 'readDebugTokenFromIndexedDB').returns( - Promise.resolve('cached-debug-token') - ); - - self.FIREBASE_APPCHECK_DEBUG_TOKEN = true; - initializeDebugMode(); - - const debugState = getDebugState(); - expect(debugState.enabled).to.be.true; - await expect(debugState.token?.promise).to.eventually.equal( - 'cached-debug-token' - ); - }); -}); diff --git a/packages-exp/app-check-exp/src/debug.ts b/packages-exp/app-check-exp/src/debug.ts deleted file mode 100644 index da6cffbfcc4..00000000000 --- a/packages-exp/app-check-exp/src/debug.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getDebugState } from './state'; -import { readOrCreateDebugTokenFromStorage } from './storage'; -import { Deferred, getGlobal } from '@firebase/util'; - -declare global { - // var must be used for global scopes - // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#type-checking-for-globalthis - // eslint-disable-next-line no-var - var FIREBASE_APPCHECK_DEBUG_TOKEN: boolean | string | undefined; -} - -export function isDebugMode(): boolean { - const debugState = getDebugState(); - return debugState.enabled; -} - -export async function getDebugToken(): Promise { - const state = getDebugState(); - - if (state.enabled && state.token) { - return state.token.promise; - } else { - // should not happen! - throw Error(` - Can't get debug token in production mode. - `); - } -} - -export function initializeDebugMode(): void { - const globals = getGlobal(); - if ( - typeof globals.FIREBASE_APPCHECK_DEBUG_TOKEN !== 'string' && - globals.FIREBASE_APPCHECK_DEBUG_TOKEN !== true - ) { - return; - } - - const debugState = getDebugState(); - debugState.enabled = true; - const deferredToken = new Deferred(); - debugState.token = deferredToken; - - if (typeof globals.FIREBASE_APPCHECK_DEBUG_TOKEN === 'string') { - deferredToken.resolve(globals.FIREBASE_APPCHECK_DEBUG_TOKEN); - } else { - deferredToken.resolve(readOrCreateDebugTokenFromStorage()); - } -} diff --git a/packages-exp/app-check-exp/src/errors.ts b/packages-exp/app-check-exp/src/errors.ts deleted file mode 100644 index 05324b639ee..00000000000 --- a/packages-exp/app-check-exp/src/errors.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorFactory, ErrorMap } from '@firebase/util'; - -export const enum AppCheckError { - ALREADY_INITIALIZED = 'already-initialized', - USE_BEFORE_ACTIVATION = 'use-before-activation', - FETCH_NETWORK_ERROR = 'fetch-network-error', - FETCH_PARSE_ERROR = 'fetch-parse-error', - FETCH_STATUS_ERROR = 'fetch-status-error', - STORAGE_OPEN = 'storage-open', - STORAGE_GET = 'storage-get', - STORAGE_WRITE = 'storage-set', - RECAPTCHA_ERROR = 'recaptcha-error' -} - -const ERRORS: ErrorMap = { - [AppCheckError.ALREADY_INITIALIZED]: - 'You have already called initializeAppCheck() for FirebaseApp {$appName} with ' + - 'different options. To avoid this error, call initializeAppCheck() with the ' + - 'same options as when it was originally called. This will return the ' + - 'already initialized instance.', - [AppCheckError.USE_BEFORE_ACTIVATION]: - 'App Check is being used before initializeAppCheck() is called for FirebaseApp {$appName}. ' + - 'Call initializeAppCheck() before instantiating other Firebase services.', - [AppCheckError.FETCH_NETWORK_ERROR]: - 'Fetch failed to connect to a network. Check Internet connection. ' + - 'Original error: {$originalErrorMessage}.', - [AppCheckError.FETCH_PARSE_ERROR]: - 'Fetch client could not parse response.' + - ' Original error: {$originalErrorMessage}.', - [AppCheckError.FETCH_STATUS_ERROR]: - 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.', - [AppCheckError.STORAGE_OPEN]: - 'Error thrown when opening storage. Original error: {$originalErrorMessage}.', - [AppCheckError.STORAGE_GET]: - 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.', - [AppCheckError.STORAGE_WRITE]: - 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.', - [AppCheckError.RECAPTCHA_ERROR]: 'ReCAPTCHA error.' -}; - -interface ErrorParams { - [AppCheckError.ALREADY_INITIALIZED]: { appName: string }; - [AppCheckError.USE_BEFORE_ACTIVATION]: { appName: string }; - [AppCheckError.FETCH_NETWORK_ERROR]: { originalErrorMessage: string }; - [AppCheckError.FETCH_PARSE_ERROR]: { originalErrorMessage: string }; - [AppCheckError.FETCH_STATUS_ERROR]: { httpStatus: number }; - [AppCheckError.STORAGE_OPEN]: { originalErrorMessage?: string }; - [AppCheckError.STORAGE_GET]: { originalErrorMessage?: string }; - [AppCheckError.STORAGE_WRITE]: { originalErrorMessage?: string }; -} - -export const ERROR_FACTORY = new ErrorFactory( - 'appCheck', - 'AppCheck', - ERRORS -); diff --git a/packages-exp/app-check-exp/src/factory.ts b/packages-exp/app-check-exp/src/factory.ts deleted file mode 100644 index 771fa70e965..00000000000 --- a/packages-exp/app-check-exp/src/factory.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AppCheck } from './public-types'; -import { FirebaseApp, _FirebaseService } from '@firebase/app-exp'; -import { FirebaseAppCheckInternal, ListenerType } from './types'; -import { - getToken, - addTokenListener, - removeTokenListener -} from './internal-api'; -import { Provider } from '@firebase/component'; -import { getState } from './state'; - -/** - * AppCheck Service class. - */ -export class AppCheckService implements AppCheck, _FirebaseService { - constructor( - public app: FirebaseApp, - public platformLoggerProvider: Provider<'platform-logger'> - ) {} - _delete(): Promise { - const { tokenObservers } = getState(this.app); - for (const tokenObserver of tokenObservers) { - removeTokenListener(this.app, tokenObserver.next); - } - return Promise.resolve(); - } -} - -export function factory( - app: FirebaseApp, - platformLoggerProvider: Provider<'platform-logger'> -): AppCheckService { - return new AppCheckService(app, platformLoggerProvider); -} - -export function internalFactory( - appCheck: AppCheckService -): FirebaseAppCheckInternal { - return { - getToken: forceRefresh => getToken(appCheck, forceRefresh), - addTokenListener: listener => - addTokenListener(appCheck, ListenerType.INTERNAL, listener), - removeTokenListener: listener => removeTokenListener(appCheck.app, listener) - }; -} diff --git a/packages-exp/app-check-exp/src/index.ts b/packages-exp/app-check-exp/src/index.ts deleted file mode 100644 index debae46b29d..00000000000 --- a/packages-exp/app-check-exp/src/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Firebase App Check - * - * @packageDocumentation - */ - -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { registerVersion, _registerComponent } from '@firebase/app-exp'; -import { - Component, - ComponentType, - InstantiationMode -} from '@firebase/component'; -import { _AppCheckComponentName } from './public-types'; -import { factory, internalFactory } from './factory'; -import { initializeDebugMode } from './debug'; -import { _AppCheckInternalComponentName } from './types'; -import { name, version } from '../package.json'; - -// Used by other Firebase packages. -export { _AppCheckInternalComponentName }; - -export * from './api'; -export * from './public-types'; - -const APP_CHECK_NAME: _AppCheckComponentName = 'app-check-exp'; -const APP_CHECK_NAME_INTERNAL: _AppCheckInternalComponentName = - 'app-check-internal'; -function registerAppCheck(): void { - // The public interface - _registerComponent( - new Component( - APP_CHECK_NAME, - container => { - // getImmediate for FirebaseApp will always succeed - const app = container.getProvider('app-exp').getImmediate(); - const platformLoggerProvider = container.getProvider('platform-logger'); - return factory(app, platformLoggerProvider); - }, - ComponentType.PUBLIC - ) - .setInstantiationMode(InstantiationMode.EXPLICIT) - /** - * Initialize app-check-internal after app-check is initialized to make AppCheck available to - * other Firebase SDKs - */ - .setInstanceCreatedCallback( - (container, _identifier, _appcheckService) => { - container.getProvider(APP_CHECK_NAME_INTERNAL).initialize(); - } - ) - ); - - // The internal interface used by other Firebase products - _registerComponent( - new Component( - APP_CHECK_NAME_INTERNAL, - container => { - const appCheck = container.getProvider('app-check-exp').getImmediate(); - return internalFactory(appCheck); - }, - ComponentType.PUBLIC - ).setInstantiationMode(InstantiationMode.EXPLICIT) - ); - - registerVersion(name, version); -} - -registerAppCheck(); -initializeDebugMode(); diff --git a/packages-exp/app-check-exp/src/indexeddb.ts b/packages-exp/app-check-exp/src/indexeddb.ts deleted file mode 100644 index 144f1656c3d..00000000000 --- a/packages-exp/app-check-exp/src/indexeddb.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp } from '@firebase/app-exp'; -import { ERROR_FACTORY, AppCheckError } from './errors'; -import { AppCheckTokenInternal } from './types'; -const DB_NAME = 'firebase-app-check-database'; -const DB_VERSION = 1; -const STORE_NAME = 'firebase-app-check-store'; -const DEBUG_TOKEN_KEY = 'debug-token'; - -let dbPromise: Promise | null = null; -function getDBPromise(): Promise { - if (dbPromise) { - return dbPromise; - } - - dbPromise = new Promise((resolve, reject) => { - try { - const request = indexedDB.open(DB_NAME, DB_VERSION); - - request.onsuccess = event => { - resolve((event.target as IDBOpenDBRequest).result); - }; - - request.onerror = event => { - reject( - ERROR_FACTORY.create(AppCheckError.STORAGE_OPEN, { - originalErrorMessage: (event.target as IDBRequest).error?.message - }) - ); - }; - - request.onupgradeneeded = event => { - const db = (event.target as IDBOpenDBRequest).result; - - // We don't use 'break' in this switch statement, the fall-through - // behavior is what we want, because if there are multiple versions between - // the old version and the current version, we want ALL the migrations - // that correspond to those versions to run, not only the last one. - // eslint-disable-next-line default-case - switch (event.oldVersion) { - case 0: - db.createObjectStore(STORE_NAME, { - keyPath: 'compositeKey' - }); - } - }; - } catch (e) { - reject( - ERROR_FACTORY.create(AppCheckError.STORAGE_OPEN, { - originalErrorMessage: e.message - }) - ); - } - }); - - return dbPromise; -} - -export function readTokenFromIndexedDB( - app: FirebaseApp -): Promise { - return read(computeKey(app)) as Promise; -} - -export function writeTokenToIndexedDB( - app: FirebaseApp, - token: AppCheckTokenInternal -): Promise { - return write(computeKey(app), token); -} - -export function writeDebugTokenToIndexedDB(token: string): Promise { - return write(DEBUG_TOKEN_KEY, token); -} - -export function readDebugTokenFromIndexedDB(): Promise { - return read(DEBUG_TOKEN_KEY) as Promise; -} - -async function write(key: string, value: unknown): Promise { - const db = await getDBPromise(); - - const transaction = db.transaction(STORE_NAME, 'readwrite'); - const store = transaction.objectStore(STORE_NAME); - const request = store.put({ - compositeKey: key, - value - }); - - return new Promise((resolve, reject) => { - request.onsuccess = _event => { - resolve(); - }; - - transaction.onerror = event => { - reject( - ERROR_FACTORY.create(AppCheckError.STORAGE_WRITE, { - originalErrorMessage: (event.target as IDBRequest).error?.message - }) - ); - }; - }); -} - -async function read(key: string): Promise { - const db = await getDBPromise(); - - const transaction = db.transaction(STORE_NAME, 'readonly'); - const store = transaction.objectStore(STORE_NAME); - const request = store.get(key); - - return new Promise((resolve, reject) => { - request.onsuccess = event => { - const result = (event.target as IDBRequest).result; - - if (result) { - resolve(result.value); - } else { - resolve(undefined); - } - }; - - transaction.onerror = event => { - reject( - ERROR_FACTORY.create(AppCheckError.STORAGE_GET, { - originalErrorMessage: (event.target as IDBRequest).error?.message - }) - ); - }; - }); -} - -function computeKey(app: FirebaseApp): string { - return `${app.options.appId}-${app.name}`; -} diff --git a/packages-exp/app-check-exp/src/internal-api.test.ts b/packages-exp/app-check-exp/src/internal-api.test.ts deleted file mode 100644 index de110851cab..00000000000 --- a/packages-exp/app-check-exp/src/internal-api.test.ts +++ /dev/null @@ -1,494 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../test/setup'; -import { expect } from 'chai'; -import { SinonStub, spy, stub, useFakeTimers } from 'sinon'; -import { deleteApp, FirebaseApp } from '@firebase/app-exp'; -import { - FAKE_SITE_KEY, - getFullApp, - getFakeCustomTokenProvider, - removegreCAPTCHAScriptsOnPage, - getFakeGreCAPTCHA -} from '../test/util'; -import { initializeAppCheck } from './api'; -import { - getToken, - addTokenListener, - removeTokenListener, - formatDummyToken, - defaultTokenErrorData -} from './internal-api'; -import * as reCAPTCHA from './recaptcha'; -import * as client from './client'; -import * as storage from './storage'; -import * as util from './util'; -import { getState, clearState, setState, getDebugState } from './state'; -import { AppCheckTokenListener } from './public-types'; -import { Deferred } from '@firebase/util'; -import { ReCaptchaV3Provider } from './providers'; -import { AppCheckService } from './factory'; -import { ListenerType } from './types'; - -const fakeRecaptchaToken = 'fake-recaptcha-token'; -const fakeRecaptchaAppCheckToken = { - token: 'fake-recaptcha-app-check-token', - expireTimeMillis: Date.now() + 60000, - issuedAtTimeMillis: 0 -}; - -const fakeCachedAppCheckToken = { - token: 'fake-cached-app-check-token', - expireTimeMillis: Date.now() + 60000, - issuedAtTimeMillis: 0 -}; - -describe('internal api', () => { - let app: FirebaseApp; - let storageReadStub: SinonStub; - let storageWriteStub: SinonStub; - - beforeEach(() => { - app = getFullApp(); - storageReadStub = stub(storage, 'readTokenFromStorage').resolves(undefined); - storageWriteStub = stub(storage, 'writeTokenToStorage'); - stub(util, 'getRecaptcha').returns(getFakeGreCAPTCHA()); - }); - - afterEach(() => { - clearState(); - removegreCAPTCHAScriptsOnPage(); - return deleteApp(app); - }); - // TODO: test error conditions - describe('getToken()', () => { - it('uses customTokenProvider to get an AppCheck token', async () => { - const customTokenProvider = getFakeCustomTokenProvider(); - const customProviderSpy = spy(customTokenProvider, 'getToken'); - - const appCheck = initializeAppCheck(app, { - provider: customTokenProvider - }); - const token = await getToken(appCheck as AppCheckService); - - expect(customProviderSpy).to.be.called; - expect(token).to.deep.equal({ - token: 'fake-custom-app-check-token' - }); - }); - - it('uses reCAPTCHA token to exchange for AppCheck token', async () => { - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - - const reCAPTCHASpy = stub(reCAPTCHA, 'getToken').returns( - Promise.resolve(fakeRecaptchaToken) - ); - const exchangeTokenStub: SinonStub = stub( - client, - 'exchangeToken' - ).returns(Promise.resolve(fakeRecaptchaAppCheckToken)); - - const token = await getToken(appCheck as AppCheckService); - - expect(reCAPTCHASpy).to.be.called; - - expect(exchangeTokenStub.args[0][0].body['recaptcha_token']).to.equal( - fakeRecaptchaToken - ); - expect(token).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); - }); - - it('resolves with a dummy token and an error if failed to get a token', async () => { - const errorStub = stub(console, 'error'); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - - const reCAPTCHASpy = stub(reCAPTCHA, 'getToken').returns( - Promise.resolve(fakeRecaptchaToken) - ); - - const error = new Error('oops, something went wrong'); - stub(client, 'exchangeToken').returns(Promise.reject(error)); - - const token = await getToken(appCheck as AppCheckService); - - expect(reCAPTCHASpy).to.be.called; - expect(token).to.deep.equal({ - token: formatDummyToken(defaultTokenErrorData), - error - }); - expect(errorStub.args[0][1].message).to.include( - 'oops, something went wrong' - ); - errorStub.restore(); - }); - - it('notifies listeners using cached token', async () => { - storageReadStub.resolves(fakeCachedAppCheckToken); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: false - }); - - const clock = useFakeTimers(); - - const listener1 = spy(); - const listener2 = spy(); - addTokenListener( - appCheck as AppCheckService, - ListenerType.INTERNAL, - listener1 - ); - addTokenListener( - appCheck as AppCheckService, - ListenerType.INTERNAL, - listener2 - ); - - await getToken(appCheck as AppCheckService); - - clock.tick(1); - - expect(listener1).to.be.calledWith({ - token: fakeCachedAppCheckToken.token - }); - expect(listener2).to.be.calledWith({ - token: fakeCachedAppCheckToken.token - }); - - clock.restore(); - }); - - it('notifies listeners using new token', async () => { - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: true - }); - - stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken)); - stub(client, 'exchangeToken').returns( - Promise.resolve(fakeRecaptchaAppCheckToken) - ); - - const listener1 = spy(); - const listener2 = spy(); - addTokenListener( - appCheck as AppCheckService, - ListenerType.INTERNAL, - listener1 - ); - addTokenListener( - appCheck as AppCheckService, - ListenerType.INTERNAL, - listener2 - ); - - await getToken(appCheck as AppCheckService); - - expect(listener1).to.be.calledWith({ - token: fakeRecaptchaAppCheckToken.token - }); - expect(listener2).to.be.calledWith({ - token: fakeRecaptchaAppCheckToken.token - }); - }); - - it('calls 3P error handler if there is an error getting a token', async () => { - stub(console, 'error'); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: true - }); - stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken)); - stub(client, 'exchangeToken').rejects('exchange error'); - const listener1 = spy(); - const errorFn1 = spy(); - - addTokenListener( - appCheck as AppCheckService, - ListenerType.EXTERNAL, - listener1, - errorFn1 - ); - - await getToken(appCheck as AppCheckService); - - expect(errorFn1).to.be.calledOnce; - expect(errorFn1.args[0][0].name).to.include('exchange error'); - }); - - it('ignores listeners that throw', async () => { - stub(console, 'error'); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: true - }); - stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken)); - stub(client, 'exchangeToken').returns( - Promise.resolve(fakeRecaptchaAppCheckToken) - ); - const listener1 = stub().throws(new Error()); - const listener2 = spy(); - - addTokenListener( - appCheck as AppCheckService, - ListenerType.INTERNAL, - listener1 - ); - addTokenListener( - appCheck as AppCheckService, - ListenerType.INTERNAL, - listener2 - ); - - await getToken(appCheck as AppCheckService); - - expect(listener1).to.be.calledWith({ - token: fakeRecaptchaAppCheckToken.token - }); - expect(listener2).to.be.calledWith({ - token: fakeRecaptchaAppCheckToken.token - }); - }); - - it('loads persisted token to memory and returns it', async () => { - const clock = useFakeTimers(); - - storageReadStub.resolves(fakeCachedAppCheckToken); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - - const clientStub = stub(client, 'exchangeToken'); - - expect(getState(app).token).to.equal(undefined); - expect(await getToken(appCheck as AppCheckService)).to.deep.equal({ - token: fakeCachedAppCheckToken.token - }); - expect(getState(app).token).to.equal(fakeCachedAppCheckToken); - expect(clientStub).has.not.been.called; - - clock.restore(); - }); - - it('persists token to storage', async () => { - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - - stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken)); - stub(client, 'exchangeToken').returns( - Promise.resolve(fakeRecaptchaAppCheckToken) - ); - storageWriteStub.resetHistory(); - const result = await getToken(appCheck as AppCheckService); - expect(result).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); - expect(storageWriteStub).has.been.calledWith( - app, - fakeRecaptchaAppCheckToken - ); - }); - - it('returns the valid token in memory without making network request', async () => { - const clock = useFakeTimers(); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - setState(app, { ...getState(app), token: fakeRecaptchaAppCheckToken }); - - const clientStub = stub(client, 'exchangeToken'); - expect(await getToken(appCheck as AppCheckService)).to.deep.equal({ - token: fakeRecaptchaAppCheckToken.token - }); - expect(clientStub).to.not.have.been.called; - - clock.restore(); - }); - - it('force to get new token when forceRefresh is true', async () => { - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - setState(app, { ...getState(app), token: fakeRecaptchaAppCheckToken }); - - stub(reCAPTCHA, 'getToken').returns(Promise.resolve(fakeRecaptchaToken)); - stub(client, 'exchangeToken').returns( - Promise.resolve({ - token: 'new-recaptcha-app-check-token', - expireTimeMillis: Date.now() + 60000, - issuedAtTimeMillis: 0 - }) - ); - - expect(await getToken(appCheck as AppCheckService, true)).to.deep.equal({ - token: 'new-recaptcha-app-check-token' - }); - }); - - it('exchanges debug token if in debug mode and there is no cached token', async () => { - const exchangeTokenStub: SinonStub = stub( - client, - 'exchangeToken' - ).returns(Promise.resolve(fakeRecaptchaAppCheckToken)); - const debugState = getDebugState(); - debugState.enabled = true; - debugState.token = new Deferred(); - debugState.token.resolve('my-debug-token'); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - - const token = await getToken(appCheck as AppCheckService); - expect(exchangeTokenStub.args[0][0].body['debug_token']).to.equal( - 'my-debug-token' - ); - expect(token).to.deep.equal({ token: fakeRecaptchaAppCheckToken.token }); - }); - }); - - describe('addTokenListener', () => { - it('adds token listeners', () => { - const listener = (): void => {}; - setState(app, { - ...getState(app), - cachedTokenPromise: Promise.resolve(undefined) - }); - - addTokenListener( - { app } as AppCheckService, - ListenerType.INTERNAL, - listener - ); - - expect(getState(app).tokenObservers[0].next).to.equal(listener); - }); - - it('starts proactively refreshing token after adding the first listener', () => { - const listener = (): void => {}; - setState(app, { - ...getState(app), - isTokenAutoRefreshEnabled: true, - cachedTokenPromise: Promise.resolve(undefined) - }); - expect(getState(app).tokenObservers.length).to.equal(0); - expect(getState(app).tokenRefresher).to.equal(undefined); - - addTokenListener( - { app } as AppCheckService, - ListenerType.INTERNAL, - listener - ); - - expect(getState(app).tokenRefresher?.isRunning()).to.be.true; - }); - - it('notifies the listener with the valid token in memory immediately', async () => { - const clock = useFakeTimers(); - - const listener = stub(); - - setState(app, { - ...getState(app), - token: { - token: `fake-memory-app-check-token`, - expireTimeMillis: Date.now() + 60000, - issuedAtTimeMillis: 0 - } - }); - - addTokenListener( - { app } as AppCheckService, - ListenerType.INTERNAL, - listener - ); - await clock.runAllAsync(); - expect(listener).to.be.calledWith({ - token: 'fake-memory-app-check-token' - }); - clock.restore(); - }); - - it('notifies the listener with the valid token in storage', done => { - storageReadStub.resolves({ - token: `fake-cached-app-check-token`, - expireTimeMillis: Date.now() + 60000, - issuedAtTimeMillis: 0 - }); - const appCheck = initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY), - isTokenAutoRefreshEnabled: true - }); - - const fakeListener: AppCheckTokenListener = token => { - expect(token).to.deep.equal({ - token: `fake-cached-app-check-token` - }); - done(); - }; - - addTokenListener( - appCheck as AppCheckService, - ListenerType.INTERNAL, - fakeListener - ); - }); - }); - - describe('removeTokenListener', () => { - it('should remove token listeners', () => { - const listener = (): void => {}; - setState(app, { - ...getState(app), - cachedTokenPromise: Promise.resolve(undefined) - }); - addTokenListener( - { app } as AppCheckService, - ListenerType.INTERNAL, - listener - ); - expect(getState(app).tokenObservers.length).to.equal(1); - - removeTokenListener(app, listener); - expect(getState(app).tokenObservers.length).to.equal(0); - }); - - it('should stop proactively refreshing token after deleting the last listener', () => { - const listener = (): void => {}; - setState(app, { ...getState(app), isTokenAutoRefreshEnabled: true }); - setState(app, { - ...getState(app), - cachedTokenPromise: Promise.resolve(undefined) - }); - - addTokenListener( - { app } as AppCheckService, - ListenerType.INTERNAL, - listener - ); - expect(getState(app).tokenObservers.length).to.equal(1); - expect(getState(app).tokenRefresher?.isRunning()).to.be.true; - - removeTokenListener(app, listener); - expect(getState(app).tokenObservers.length).to.equal(0); - expect(getState(app).tokenRefresher?.isRunning()).to.be.false; - }); - }); -}); diff --git a/packages-exp/app-check-exp/src/internal-api.ts b/packages-exp/app-check-exp/src/internal-api.ts deleted file mode 100644 index 2ee5c58053b..00000000000 --- a/packages-exp/app-check-exp/src/internal-api.ts +++ /dev/null @@ -1,314 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp } from '@firebase/app-exp'; -import { - AppCheckTokenResult, - AppCheckTokenInternal, - AppCheckTokenObserver, - ListenerType -} from './types'; -import { AppCheckTokenListener } from './public-types'; -import { getState, setState } from './state'; -import { TOKEN_REFRESH_TIME } from './constants'; -import { Refresher } from './proactive-refresh'; -import { ensureActivated } from './util'; -import { exchangeToken, getExchangeDebugTokenRequest } from './client'; -import { writeTokenToStorage } from './storage'; -import { getDebugToken, isDebugMode } from './debug'; -import { base64 } from '@firebase/util'; -import { logger } from './logger'; -import { AppCheckService } from './factory'; - -// Initial hardcoded value agreed upon across platforms for initial launch. -// Format left open for possible dynamic error values and other fields in the future. -export const defaultTokenErrorData = { error: 'UNKNOWN_ERROR' }; - -/** - * Stringify and base64 encode token error data. - * - * @param tokenError Error data, currently hardcoded. - */ -export function formatDummyToken( - tokenErrorData: Record -): string { - return base64.encodeString( - JSON.stringify(tokenErrorData), - /* webSafe= */ false - ); -} - -/** - * This function always resolves. - * The result will contain an error field if there is any error. - * In case there is an error, the token field in the result will be populated with a dummy value - */ -export async function getToken( - appCheck: AppCheckService, - forceRefresh = false -): Promise { - const app = appCheck.app; - ensureActivated(app); - - const state = getState(app); - - /** - * First check if there is a token in memory from a previous `getToken()` call. - */ - let token: AppCheckTokenInternal | undefined = state.token; - let error: Error | undefined = undefined; - - /** - * If there is no token in memory, try to load token from indexedDB. - */ - if (!token) { - // cachedTokenPromise contains the token found in IndexedDB or undefined if not found. - const cachedToken = await state.cachedTokenPromise; - if (cachedToken && isValid(cachedToken)) { - token = cachedToken; - - setState(app, { ...state, token }); - // notify all listeners with the cached token - notifyTokenListeners(app, { token: token.token }); - } - } - - // Return the cached token (from either memory or indexedDB) if it's valid - if (!forceRefresh && token && isValid(token)) { - return { - token: token.token - }; - } - - /** - * DEBUG MODE - * If debug mode is set, and there is no cached token, fetch a new App - * Check token using the debug token, and return it directly. - */ - if (isDebugMode()) { - const tokenFromDebugExchange: AppCheckTokenInternal = await exchangeToken( - getExchangeDebugTokenRequest(app, await getDebugToken()), - appCheck.platformLoggerProvider - ); - // Write debug token to indexedDB. - await writeTokenToStorage(app, tokenFromDebugExchange); - // Write debug token to state. - setState(app, { ...state, token: tokenFromDebugExchange }); - return { token: tokenFromDebugExchange.token }; - } - - /** - * request a new token - */ - try { - // state.provider is populated in initializeAppCheck() - // ensureActivated() at the top of this function checks that - // initializeAppCheck() has been called. - token = await state.provider!.getToken(); - } catch (e) { - // `getToken()` should never throw, but logging error text to console will aid debugging. - logger.error(e); - error = e; - } - - let interopTokenResult: AppCheckTokenResult | undefined; - if (!token) { - // if token is undefined, there must be an error. - // we return a dummy token along with the error - interopTokenResult = makeDummyTokenResult(error!); - } else { - interopTokenResult = { - token: token.token - }; - // write the new token to the memory state as well as the persistent storage. - // Only do it if we got a valid new token - setState(app, { ...state, token }); - await writeTokenToStorage(app, token); - } - - notifyTokenListeners(app, interopTokenResult); - return interopTokenResult; -} - -export function addTokenListener( - appCheck: AppCheckService, - type: ListenerType, - listener: AppCheckTokenListener, - onError?: (error: Error) => void -): void { - const { app } = appCheck; - const state = getState(app); - const tokenObserver: AppCheckTokenObserver = { - next: listener, - error: onError, - type - }; - const newState = { - ...state, - tokenObservers: [...state.tokenObservers, tokenObserver] - }; - /** - * Invoke the listener with the valid token, then start the token refresher - */ - if (!newState.tokenRefresher) { - const tokenRefresher = createTokenRefresher(appCheck); - newState.tokenRefresher = tokenRefresher; - } - - // Create the refresher but don't start it if `isTokenAutoRefreshEnabled` - // is not true. - if (!newState.tokenRefresher.isRunning() && state.isTokenAutoRefreshEnabled) { - newState.tokenRefresher.start(); - } - - // Invoke the listener async immediately if there is a valid token - // in memory. - if (state.token && isValid(state.token)) { - const validToken = state.token; - Promise.resolve() - .then(() => listener({ token: validToken.token })) - .catch(() => { - /* we don't care about exceptions thrown in listeners */ - }); - } else if (state.token == null) { - // Only check cache if there was no token. If the token was invalid, - // skip this and rely on exchange endpoint. - void state - .cachedTokenPromise! // Storage token promise. Always populated in `activate()`. - .then(cachedToken => { - if (cachedToken && isValid(cachedToken)) { - listener({ token: cachedToken.token }); - } - }) - .catch(() => { - /** Ignore errors in listeners. */ - }); - } - - setState(app, newState); -} - -export function removeTokenListener( - app: FirebaseApp, - listener: AppCheckTokenListener -): void { - const state = getState(app); - - const newObservers = state.tokenObservers.filter( - tokenObserver => tokenObserver.next !== listener - ); - if ( - newObservers.length === 0 && - state.tokenRefresher && - state.tokenRefresher.isRunning() - ) { - state.tokenRefresher.stop(); - } - - setState(app, { - ...state, - tokenObservers: newObservers - }); -} - -function createTokenRefresher(appCheck: AppCheckService): Refresher { - const { app } = appCheck; - return new Refresher( - // Keep in mind when this fails for any reason other than the ones - // for which we should retry, it will effectively stop the proactive refresh. - async () => { - const state = getState(app); - // If there is no token, we will try to load it from storage and use it - // If there is a token, we force refresh it because we know it's going to expire soon - let result; - if (!state.token) { - result = await getToken(appCheck); - } else { - result = await getToken(appCheck, true); - } - - // getToken() always resolves. In case the result has an error field defined, it means the operation failed, and we should retry. - if (result.error) { - throw result.error; - } - }, - () => { - // TODO: when should we retry? - return true; - }, - () => { - const state = getState(app); - - if (state.token) { - // issuedAtTime + (50% * total TTL) + 5 minutes - let nextRefreshTimeMillis = - state.token.issuedAtTimeMillis + - (state.token.expireTimeMillis - state.token.issuedAtTimeMillis) * - 0.5 + - 5 * 60 * 1000; - // Do not allow refresh time to be past (expireTime - 5 minutes) - const latestAllowableRefresh = - state.token.expireTimeMillis - 5 * 60 * 1000; - nextRefreshTimeMillis = Math.min( - nextRefreshTimeMillis, - latestAllowableRefresh - ); - return Math.max(0, nextRefreshTimeMillis - Date.now()); - } else { - return 0; - } - }, - TOKEN_REFRESH_TIME.RETRIAL_MIN_WAIT, - TOKEN_REFRESH_TIME.RETRIAL_MAX_WAIT - ); -} - -function notifyTokenListeners( - app: FirebaseApp, - token: AppCheckTokenResult -): void { - const observers = getState(app).tokenObservers; - - for (const observer of observers) { - try { - if (observer.type === ListenerType.EXTERNAL && token.error != null) { - // If this listener was added by a 3P call, send any token error to - // the supplied error handler. A 3P observer always has an error - // handler. - observer.error!(token.error); - } else { - // If the token has no error field, always return the token. - // If this is a 2P listener, return the token, whether or not it - // has an error field. - observer.next(token); - } - } catch (e) { - // Errors in the listener function itself are always ignored. - } - } -} - -export function isValid(token: AppCheckTokenInternal): boolean { - return token.expireTimeMillis - Date.now() > 0; -} - -function makeDummyTokenResult(error: Error): AppCheckTokenResult { - return { - token: formatDummyToken(defaultTokenErrorData), - error - }; -} diff --git a/packages-exp/app-check-exp/src/logger.ts b/packages-exp/app-check-exp/src/logger.ts deleted file mode 100644 index 45896c6a779..00000000000 --- a/packages-exp/app-check-exp/src/logger.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Logger } from '@firebase/logger'; - -export const logger = new Logger('@firebase/app-check'); diff --git a/packages-exp/app-check-exp/src/proactive-refresh.test.ts b/packages-exp/app-check-exp/src/proactive-refresh.test.ts deleted file mode 100644 index e0944ef43ff..00000000000 --- a/packages-exp/app-check-exp/src/proactive-refresh.test.ts +++ /dev/null @@ -1,201 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../test/setup'; -import { useFakeTimers } from 'sinon'; -import { expect } from 'chai'; -import { Deferred } from '@firebase/util'; -import { Refresher } from './proactive-refresh'; - -describe('proactive refresh', () => { - it('throws if lowerbound is greater than the upperbound', () => { - expect( - () => - new Refresher( - () => Promise.resolve(), - () => false, - () => 0, - 100, - 99 - ) - ).to.throw(/Proactive refresh lower bound greater than upper bound!/); - }); - - it('runs operation after wait', async () => { - const clock = useFakeTimers(); - const operations = [new Deferred(), new Deferred(), new Deferred()]; - let counter = 0; - const waitTime = 10; - const refresher = new Refresher( - () => { - const operation = operations[counter++]; - operation.resolve(); - return operation.promise; - }, - () => false, - () => waitTime, - 1, - 100 - ); - - expect(refresher.isRunning()).to.be.false; - refresher.start(); - expect(refresher.isRunning()).to.be.true; - - clock.tick(waitTime); - await expect(operations[0].promise).to.eventually.fulfilled; - clock.tick(waitTime); - await expect(operations[1].promise).to.eventually.fulfilled; - clock.tick(waitTime); - await expect(operations[2].promise).to.eventually.fulfilled; - - clock.restore(); - }); - - it('retries on retriable errors', async () => { - const waitTime = 10; - let counter = 0; - const successOperation = new Deferred(); - const refresher = new Refresher( - () => { - if (counter++ === 0) { - return Promise.reject('Error but retriable'); - } else { - successOperation.resolve(); - return successOperation.promise; - } - }, - error => (error as string).includes('Error but retriable'), - () => waitTime, - 1, - 100 - ); - - refresher.start(); - - await expect(successOperation.promise).to.eventually.fulfilled; - expect(refresher.isRunning()).to.be.true; - }); - - it('does not retry and stop refreshing on non-retriable errors', async () => { - const waitTime = 10; - const retryCheck = new Deferred(); - const refresher = new Refresher( - () => Promise.reject('non-retriable'), - error => { - retryCheck.resolve(); - return (error as string).includes('Error but retriable'); - }, - () => waitTime, - 1, - 100 - ); - - refresher.start(); - - await retryCheck.promise; - expect(refresher.isRunning()).to.be.false; - }); - - it('backs off exponentially when retrying', async () => { - const clock = useFakeTimers(); - const minWaitTime = 10; - const maxWaitTime = 100; - let counter = 0; - const operations = [new Deferred(), new Deferred()]; - const refresher = new Refresher( - () => { - operations[counter++].resolve(); - return Promise.reject('Error but retriable'); - }, - error => (error as string).includes('Error but retriable'), - () => minWaitTime, - minWaitTime, - maxWaitTime - ); - - refresher.start(); - - clock.tick(minWaitTime); - - await expect(operations[0].promise).to.eventually.fulfilled; - clock.tick(minWaitTime * 2); - await expect(operations[1].promise).to.eventually.fulfilled; - - refresher.stop(); - clock.restore(); - }); - - it('can be stopped during wait', async () => { - const clock = useFakeTimers(); - const waitTime = 10; - const operation = new Deferred(); - const refresher = new Refresher( - () => { - operation.resolve(); - return operation.promise; - }, - _error => false, - () => waitTime, - 10, - 100 - ); - - refresher.start(); - clock.tick(0.5 * waitTime); - refresher.stop(); - clock.tick(waitTime); - - operation.reject('not resolved'); - await expect(operation.promise).to.eventually.rejectedWith('not resolved'); - expect(refresher.isRunning()).to.be.false; - clock.restore(); - }); - - it('can be restarted after being stopped', async () => { - const clock = useFakeTimers(); - const waitTime = 10; - const operation = new Deferred(); - const operationAfterRestart = new Deferred(); - const refresher = new Refresher( - () => { - operation.resolve(); - operationAfterRestart.resolve(); - return operation.promise; - }, - _error => false, - () => waitTime, - 10, - 100 - ); - - refresher.start(); - clock.tick(0.5 * waitTime); - refresher.stop(); - clock.tick(waitTime); - - operation.reject('not resolved'); - await expect(operation.promise).to.eventually.rejectedWith('not resolved'); - expect(refresher.isRunning()).to.be.false; - - refresher.start(); - clock.tick(waitTime); - await expect(operationAfterRestart.promise).to.eventually.fulfilled; - - clock.restore(); - }); -}); diff --git a/packages-exp/app-check-exp/src/proactive-refresh.ts b/packages-exp/app-check-exp/src/proactive-refresh.ts deleted file mode 100644 index 89bc046935b..00000000000 --- a/packages-exp/app-check-exp/src/proactive-refresh.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Deferred } from '@firebase/util'; - -/** - * Port from auth proactiverefresh.js - * - */ -// TODO: move it to @firebase/util? -// TODO: allow to config whether refresh should happen in the background -export class Refresher { - private pending: Deferred | null = null; - private nextErrorWaitInterval: number; - constructor( - private readonly operation: () => Promise, - private readonly retryPolicy: (error: unknown) => boolean, - private readonly getWaitDuration: () => number, - private readonly lowerBound: number, - private readonly upperBound: number - ) { - this.nextErrorWaitInterval = lowerBound; - - if (lowerBound > upperBound) { - throw new Error( - 'Proactive refresh lower bound greater than upper bound!' - ); - } - } - - start(): void { - this.nextErrorWaitInterval = this.lowerBound; - this.process(true).catch(() => { - /* we don't care about the result */ - }); - } - - stop(): void { - if (this.pending) { - this.pending.reject('cancelled'); - this.pending = null; - } - } - - isRunning(): boolean { - return !!this.pending; - } - - private async process(hasSucceeded: boolean): Promise { - this.stop(); - try { - this.pending = new Deferred(); - await sleep(this.getNextRun(hasSucceeded)); - - // Why do we resolve a promise, then immediate wait for it? - // We do it to make the promise chain cancellable. - // We can call stop() which rejects the promise before the following line execute, which makes - // the code jump to the catch block. - // TODO: unit test this - this.pending.resolve(); - await this.pending.promise; - this.pending = new Deferred(); - await this.operation(); - - this.pending.resolve(); - await this.pending.promise; - - this.process(true).catch(() => { - /* we don't care about the result */ - }); - } catch (error) { - if (this.retryPolicy(error)) { - this.process(false).catch(() => { - /* we don't care about the result */ - }); - } else { - this.stop(); - } - } - } - - private getNextRun(hasSucceeded: boolean): number { - if (hasSucceeded) { - // If last operation succeeded, reset next error wait interval and return - // the default wait duration. - this.nextErrorWaitInterval = this.lowerBound; - // Return typical wait duration interval after a successful operation. - return this.getWaitDuration(); - } else { - // Get next error wait interval. - const currentErrorWaitInterval = this.nextErrorWaitInterval; - // Double interval for next consecutive error. - this.nextErrorWaitInterval *= 2; - // Make sure next wait interval does not exceed the maximum upper bound. - if (this.nextErrorWaitInterval > this.upperBound) { - this.nextErrorWaitInterval = this.upperBound; - } - return currentErrorWaitInterval; - } - } -} - -function sleep(ms: number): Promise { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -} diff --git a/packages-exp/app-check-exp/src/providers.ts b/packages-exp/app-check-exp/src/providers.ts deleted file mode 100644 index b04afc8b9af..00000000000 --- a/packages-exp/app-check-exp/src/providers.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp, _getProvider } from '@firebase/app-exp'; -import { Provider } from '@firebase/component'; -import { issuedAtTime } from '@firebase/util'; -import { exchangeToken, getExchangeRecaptchaTokenRequest } from './client'; -import { AppCheckError, ERROR_FACTORY } from './errors'; -import { CustomProviderOptions } from './public-types'; -import { - getToken as getReCAPTCHAToken, - initialize as initializeRecaptcha -} from './recaptcha'; -import { AppCheckProvider, AppCheckTokenInternal } from './types'; - -/** - * App Check provider that can obtain a reCAPTCHA V3 token and exchange it - * for an App Check token. - * - * @public - */ -export class ReCaptchaV3Provider implements AppCheckProvider { - private _app?: FirebaseApp; - private _platformLoggerProvider?: Provider<'platform-logger'>; - /** - * Create a ReCaptchaV3Provider instance. - * @param siteKey - ReCAPTCHA V3 siteKey. - */ - constructor(private _siteKey: string) {} - - /** - * Returns an App Check token. - * @internal - */ - async getToken(): Promise { - if (!this._app || !this._platformLoggerProvider) { - // This should only occur if user has not called initializeAppCheck(). - // We don't have an appName to provide if so. - // This should already be caught in the top level `getToken()` function. - throw ERROR_FACTORY.create(AppCheckError.USE_BEFORE_ACTIVATION, { - appName: '' - }); - } - const attestedClaimsToken = await getReCAPTCHAToken(this._app).catch(_e => { - // reCaptcha.execute() throws null which is not very descriptive. - throw ERROR_FACTORY.create(AppCheckError.RECAPTCHA_ERROR); - }); - return exchangeToken( - getExchangeRecaptchaTokenRequest(this._app, attestedClaimsToken), - this._platformLoggerProvider - ); - } - - /** - * @internal - */ - initialize(app: FirebaseApp): void { - this._app = app; - this._platformLoggerProvider = _getProvider(app, 'platform-logger'); - initializeRecaptcha(app, this._siteKey).catch(() => { - /* we don't care about the initialization result */ - }); - } - - /** - * @internal - */ - isEqual(otherProvider: unknown): boolean { - if (otherProvider instanceof ReCaptchaV3Provider) { - return this._siteKey === otherProvider._siteKey; - } else { - return false; - } - } -} - -/** - * Custom provider class. - * @public - */ -export class CustomProvider implements AppCheckProvider { - private _app?: FirebaseApp; - - constructor(private _customProviderOptions: CustomProviderOptions) {} - - /** - * @internal - */ - async getToken(): Promise { - if (!this._app) { - // This should only occur if user has not called initializeAppCheck(). - // We don't have an appName to provide if so. - // This should already be caught in the top level `getToken()` function. - throw ERROR_FACTORY.create(AppCheckError.USE_BEFORE_ACTIVATION, { - appName: '' - }); - } - // custom provider - const customToken = await this._customProviderOptions.getToken(); - // Try to extract IAT from custom token, in case this token is not - // being newly issued. JWT timestamps are in seconds since epoch. - const issuedAtTimeSeconds = issuedAtTime(customToken.token); - // Very basic validation, use current timestamp as IAT if JWT - // has no `iat` field or value is out of bounds. - const issuedAtTimeMillis = - issuedAtTimeSeconds !== null && - issuedAtTimeSeconds < Date.now() && - issuedAtTimeSeconds > 0 - ? issuedAtTimeSeconds * 1000 - : Date.now(); - - return { ...customToken, issuedAtTimeMillis }; - } - - /** - * @internal - */ - initialize(app: FirebaseApp): void { - this._app = app; - } - - /** - * @internal - */ - isEqual(otherProvider: unknown): boolean { - if (otherProvider instanceof CustomProvider) { - return ( - this._customProviderOptions.getToken.toString() === - otherProvider._customProviderOptions.getToken.toString() - ); - } else { - return false; - } - } -} diff --git a/packages-exp/app-check-exp/src/recaptcha.test.ts b/packages-exp/app-check-exp/src/recaptcha.test.ts deleted file mode 100644 index 90a8335bcdb..00000000000 --- a/packages-exp/app-check-exp/src/recaptcha.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../test/setup'; -import { expect } from 'chai'; -import { stub } from 'sinon'; -import { deleteApp, FirebaseApp } from '@firebase/app-exp'; -import { - getFullApp, - getFakeGreCAPTCHA, - removegreCAPTCHAScriptsOnPage, - findgreCAPTCHAScriptsOnPage, - FAKE_SITE_KEY -} from '../test/util'; -import { initialize, getToken } from './recaptcha'; -import * as utils from './util'; -import { getState } from './state'; -import { Deferred } from '@firebase/util'; -import { initializeAppCheck } from './api'; -import { ReCaptchaV3Provider } from './providers'; - -describe('recaptcha', () => { - let app: FirebaseApp; - - beforeEach(() => { - app = getFullApp(); - }); - - afterEach(() => { - removegreCAPTCHAScriptsOnPage(); - return deleteApp(app); - }); - - describe('initialize()', () => { - it('sets reCAPTCHAState', async () => { - self.grecaptcha = getFakeGreCAPTCHA(); - expect(getState(app).reCAPTCHAState).to.equal(undefined); - await initialize(app, FAKE_SITE_KEY); - expect(getState(app).reCAPTCHAState?.initialized).to.be.instanceof( - Deferred - ); - }); - - it('loads reCAPTCHA script if it was not loaded already', async () => { - const fakeRecaptcha = getFakeGreCAPTCHA(); - let count = 0; - stub(utils, 'getRecaptcha').callsFake(() => { - count++; - if (count === 1) { - return undefined; - } - - return fakeRecaptcha; - }); - - expect(findgreCAPTCHAScriptsOnPage().length).to.equal(0); - await initialize(app, FAKE_SITE_KEY); - expect(findgreCAPTCHAScriptsOnPage().length).to.equal(1); - }); - - it('creates invisible widget', async () => { - const grecaptchaFake = getFakeGreCAPTCHA(); - const renderStub = stub(grecaptchaFake, 'render').callThrough(); - self.grecaptcha = grecaptchaFake; - - await initialize(app, FAKE_SITE_KEY); - - expect(renderStub).to.be.calledWith(`fire_app_check_${app.name}`, { - sitekey: FAKE_SITE_KEY, - size: 'invisible' - }); - - expect(getState(app).reCAPTCHAState?.widgetId).to.equal('fake_widget_1'); - }); - }); - - describe('getToken()', () => { - it('throws if AppCheck has not been activated yet', () => { - return expect(getToken(app)).to.eventually.rejectedWith( - /appCheck\/use-before-activation/ - ); - }); - - it('calls recaptcha.execute with correct widgetId', async () => { - const grecaptchaFake = getFakeGreCAPTCHA(); - const executeStub = stub(grecaptchaFake, 'execute').returns( - Promise.resolve('fake-recaptcha-token') - ); - self.grecaptcha = grecaptchaFake; - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - await getToken(app); - - expect(executeStub).to.have.been.calledWith('fake_widget_1', { - action: 'fire_app_check' - }); - }); - - it('resolves with token returned by recaptcha.execute', async () => { - const grecaptchaFake = getFakeGreCAPTCHA(); - stub(grecaptchaFake, 'execute').returns( - Promise.resolve('fake-recaptcha-token') - ); - self.grecaptcha = grecaptchaFake; - initializeAppCheck(app, { - provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) - }); - const token = await getToken(app); - - expect(token).to.equal('fake-recaptcha-token'); - }); - }); -}); diff --git a/packages-exp/app-check-exp/src/recaptcha.ts b/packages-exp/app-check-exp/src/recaptcha.ts deleted file mode 100644 index 3fd1bcfed73..00000000000 --- a/packages-exp/app-check-exp/src/recaptcha.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp } from '@firebase/app-exp'; -import { getState, setState } from './state'; -import { Deferred } from '@firebase/util'; -import { getRecaptcha, ensureActivated } from './util'; - -export const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js'; - -export function initialize( - app: FirebaseApp, - siteKey: string -): Promise { - const state = getState(app); - const initialized = new Deferred(); - - setState(app, { ...state, reCAPTCHAState: { initialized } }); - - const divId = `fire_app_check_${app.name}`; - const invisibleDiv = document.createElement('div'); - invisibleDiv.id = divId; - invisibleDiv.style.display = 'none'; - - document.body.appendChild(invisibleDiv); - - const grecaptcha = getRecaptcha(); - if (!grecaptcha) { - loadReCAPTCHAScript(() => { - const grecaptcha = getRecaptcha(); - - if (!grecaptcha) { - // it shouldn't happen. - throw new Error('no recaptcha'); - } - grecaptcha.ready(() => { - // Invisible widgets allow us to set a different siteKey for each widget, so we use them to support multiple apps - renderInvisibleWidget(app, siteKey, grecaptcha, divId); - initialized.resolve(grecaptcha); - }); - }); - } else { - grecaptcha.ready(() => { - renderInvisibleWidget(app, siteKey, grecaptcha, divId); - initialized.resolve(grecaptcha); - }); - } - - return initialized.promise; -} - -export async function getToken(app: FirebaseApp): Promise { - ensureActivated(app); - - // ensureActivated() guarantees that reCAPTCHAState is set - const reCAPTCHAState = getState(app).reCAPTCHAState!; - const recaptcha = await reCAPTCHAState.initialized.promise; - - return new Promise((resolve, _reject) => { - // Updated after initialization is complete. - const reCAPTCHAState = getState(app).reCAPTCHAState!; - recaptcha.ready(() => { - resolve( - // widgetId is guaranteed to be available if reCAPTCHAState.initialized.promise resolved. - recaptcha.execute(reCAPTCHAState.widgetId!, { - action: 'fire_app_check' - }) - ); - }); - }); -} - -/** - * - * @param app - * @param container - Id of a HTML element. - */ -function renderInvisibleWidget( - app: FirebaseApp, - siteKey: string, - grecaptcha: GreCAPTCHA, - container: string -): void { - const widgetId = grecaptcha.render(container, { - sitekey: siteKey, - size: 'invisible' - }); - - const state = getState(app); - - setState(app, { - ...state, - reCAPTCHAState: { - ...state.reCAPTCHAState!, // state.reCAPTCHAState is set in the initialize() - widgetId - } - }); -} - -function loadReCAPTCHAScript(onload: () => void): void { - const script = document.createElement('script'); - script.src = `${RECAPTCHA_URL}`; - script.onload = onload; - document.head.appendChild(script); -} - -declare global { - interface Window { - grecaptcha: GreCAPTCHA | undefined; - } -} - -export interface GreCAPTCHA { - ready: (callback: () => void) => void; - execute: (siteKey: string, options: { action: string }) => Promise; - render: ( - container: string | HTMLElement, - parameters: GreCAPTCHARenderOption - ) => string; -} - -export interface GreCAPTCHARenderOption { - sitekey: string; - size: 'invisible'; -} diff --git a/packages-exp/app-check-exp/src/state.ts b/packages-exp/app-check-exp/src/state.ts deleted file mode 100644 index 1d33b1889ed..00000000000 --- a/packages-exp/app-check-exp/src/state.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp } from '@firebase/app-exp'; -import { - AppCheckProvider, - AppCheckTokenInternal, - AppCheckTokenObserver -} from './types'; -import { Refresher } from './proactive-refresh'; -import { Deferred } from '@firebase/util'; -import { GreCAPTCHA } from './recaptcha'; -export interface AppCheckState { - activated: boolean; - tokenObservers: AppCheckTokenObserver[]; - provider?: AppCheckProvider; - token?: AppCheckTokenInternal; - cachedTokenPromise?: Promise; - tokenRefresher?: Refresher; - reCAPTCHAState?: ReCAPTCHAState; - isTokenAutoRefreshEnabled?: boolean; -} - -export interface ReCAPTCHAState { - initialized: Deferred; - widgetId?: string; -} - -export interface DebugState { - enabled: boolean; - token?: Deferred; -} - -const APP_CHECK_STATES = new Map(); -export const DEFAULT_STATE: AppCheckState = { - activated: false, - tokenObservers: [] -}; - -const DEBUG_STATE: DebugState = { - enabled: false -}; - -export function getState(app: FirebaseApp): AppCheckState { - return APP_CHECK_STATES.get(app) || DEFAULT_STATE; -} - -export function setState(app: FirebaseApp, state: AppCheckState): void { - APP_CHECK_STATES.set(app, state); -} - -// for testing only -export function clearState(): void { - APP_CHECK_STATES.clear(); - DEBUG_STATE.enabled = false; - DEBUG_STATE.token = undefined; -} - -export function getDebugState(): DebugState { - return DEBUG_STATE; -} diff --git a/packages-exp/app-check-exp/src/storage.test.ts b/packages-exp/app-check-exp/src/storage.test.ts deleted file mode 100644 index 763dbf3e661..00000000000 --- a/packages-exp/app-check-exp/src/storage.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../test/setup'; -import { writeTokenToStorage, readTokenFromStorage } from './storage'; -import * as indexeddbOperations from './indexeddb'; -import { getFakeApp } from '../test/util'; -import * as util from '@firebase/util'; -import { logger } from './logger'; -import { expect } from 'chai'; -import { stub } from 'sinon'; - -describe('Storage', () => { - const app = getFakeApp(); - const fakeToken = { - token: 'fake-app-check-token', - expireTimeMillis: 345, - issuedAtTimeMillis: 0 - }; - - it('sets and gets appCheck token to indexeddb', async () => { - await writeTokenToStorage(app, fakeToken); - expect(await readTokenFromStorage(app)).to.deep.equal(fakeToken); - }); - - it('no op for writeTokenToStorage() if indexeddb is not available', async () => { - stub(util, 'isIndexedDBAvailable').returns(false); - await writeTokenToStorage(app, fakeToken); - expect(await readTokenFromStorage(app)).to.equal(undefined); - }); - - it('writeTokenToStorage() still resolves if writing to indexeddb failed', async () => { - const warnStub = stub(logger, 'warn'); - stub(indexeddbOperations, 'writeTokenToIndexedDB').returns( - Promise.reject('something went wrong!') - ); - await expect(writeTokenToStorage(app, fakeToken)).to.eventually.fulfilled; - expect(warnStub.args[0][0]).to.include('something went wrong!'); - warnStub.restore(); - }); - - it('resolves with undefined if indexeddb is not available', async () => { - stub(util, 'isIndexedDBAvailable').returns(false); - expect(await readTokenFromStorage(app)).to.equal(undefined); - }); - - it('resolves with undefined if reading indexeddb failed', async () => { - const warnStub = stub(logger, 'warn'); - stub(indexeddbOperations, 'readTokenFromIndexedDB').returns( - Promise.reject('something went wrong!') - ); - expect(await readTokenFromStorage(app)).to.equal(undefined); - expect(warnStub.args[0][0]).to.include('something went wrong!'); - warnStub.restore(); - }); -}); diff --git a/packages-exp/app-check-exp/src/storage.ts b/packages-exp/app-check-exp/src/storage.ts deleted file mode 100644 index ae2b39c5195..00000000000 --- a/packages-exp/app-check-exp/src/storage.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { uuidv4 } from './util'; -import { FirebaseApp } from '@firebase/app-exp'; -import { isIndexedDBAvailable } from '@firebase/util'; -import { - readDebugTokenFromIndexedDB, - readTokenFromIndexedDB, - writeDebugTokenToIndexedDB, - writeTokenToIndexedDB -} from './indexeddb'; -import { logger } from './logger'; -import { AppCheckTokenInternal } from './types'; - -/** - * Always resolves. In case of an error reading from indexeddb, resolve with undefined - */ -export async function readTokenFromStorage( - app: FirebaseApp -): Promise { - if (isIndexedDBAvailable()) { - let token = undefined; - try { - token = await readTokenFromIndexedDB(app); - } catch (e) { - // swallow the error and return undefined - logger.warn(`Failed to read token from IndexedDB. Error: ${e}`); - } - return token; - } - - return undefined; -} - -/** - * Always resolves. In case of an error writing to indexeddb, print a warning and resolve the promise - */ -export function writeTokenToStorage( - app: FirebaseApp, - token: AppCheckTokenInternal -): Promise { - if (isIndexedDBAvailable()) { - return writeTokenToIndexedDB(app, token).catch(e => { - // swallow the error and resolve the promise - logger.warn(`Failed to write token to IndexedDB. Error: ${e}`); - }); - } - - return Promise.resolve(); -} - -export async function readOrCreateDebugTokenFromStorage(): Promise { - /** - * Theoretically race condition can happen if we read, then write in 2 separate transactions. - * But it won't happen here, because this function will be called exactly once. - */ - let existingDebugToken: string | undefined = undefined; - try { - existingDebugToken = await readDebugTokenFromIndexedDB(); - } catch (_e) { - // failed to read from indexeddb. We assume there is no existing debug token, and generate a new one. - } - - if (!existingDebugToken) { - // create a new debug token - const newToken = uuidv4(); - // We don't need to block on writing to indexeddb - // In case persistence failed, a new debug token will be generated everytime the page is refreshed. - // It renders the debug token useless because you have to manually register(whitelist) the new token in the firebase console again and again. - // If you see this error trying to use debug token, it probably means you are using a browser that doesn't support indexeddb. - // You should switch to a different browser that supports indexeddb - writeDebugTokenToIndexedDB(newToken).catch(e => - logger.warn(`Failed to persist debug token to IndexedDB. Error: ${e}`) - ); - // Not using logger because I don't think we ever want this accidentally hidden? - console.log( - `App Check debug token: ${newToken}. You will need to add it to your app's App Check settings in the Firebase console for it to work` - ); - return newToken; - } else { - return existingDebugToken; - } -} diff --git a/packages-exp/app-check-exp/src/util.ts b/packages-exp/app-check-exp/src/util.ts deleted file mode 100644 index 26aa784ad56..00000000000 --- a/packages-exp/app-check-exp/src/util.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { GreCAPTCHA } from './recaptcha'; -import { getState } from './state'; -import { ERROR_FACTORY, AppCheckError } from './errors'; -import { FirebaseApp } from '@firebase/app-exp'; - -export function getRecaptcha(): GreCAPTCHA | undefined { - return self.grecaptcha; -} - -export function ensureActivated(app: FirebaseApp): void { - if (!getState(app).activated) { - throw ERROR_FACTORY.create(AppCheckError.USE_BEFORE_ACTIVATION, { - appName: app.name - }); - } -} - -/** - * Copied from https://stackoverflow.com/a/2117523 - */ -export function uuidv4(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { - const r = (Math.random() * 16) | 0, - v = c === 'x' ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); -} diff --git a/packages-exp/app-check-exp/test/util.ts b/packages-exp/app-check-exp/test/util.ts deleted file mode 100644 index 5018ba8f4d6..00000000000 --- a/packages-exp/app-check-exp/test/util.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - FirebaseApp, - initializeApp, - _registerComponent -} from '@firebase/app-exp'; -import { GreCAPTCHA, RECAPTCHA_URL } from '../src/recaptcha'; -import { - Provider, - ComponentContainer, - Component, - ComponentType -} from '@firebase/component'; -import { PlatformLoggerService } from '@firebase/app-exp/dist/packages-exp/app-exp/src/types'; -import { AppCheckService } from '../src/factory'; -import { AppCheck, CustomProvider } from '../src'; - -export const FAKE_SITE_KEY = 'fake-site-key'; - -const fakeConfig = { - apiKey: 'apiKey', - projectId: 'projectId', - authDomain: 'authDomain', - messagingSenderId: 'messagingSenderId', - databaseURL: 'databaseUrl', - storageBucket: 'storageBucket', - appId: '1:777777777777:web:d93b5ca1475efe57' -}; - -export function getFakeApp(overrides: Record = {}): FirebaseApp { - return { - name: 'appName', - options: fakeConfig, - automaticDataCollectionEnabled: true, - ...overrides - }; -} - -export function getFakeAppCheck(app: FirebaseApp): AppCheck { - return { - app, - platformLoggerProvider: getFakePlatformLoggingProvider() - } as AppCheck; -} - -export function getFullApp(): FirebaseApp { - const app = initializeApp(fakeConfig); - _registerComponent( - new Component( - 'platform-logger', - () => { - return {} as PlatformLoggerService; - }, - ComponentType.PUBLIC - ) - ); - _registerComponent( - new Component( - 'app-check-exp', - () => { - return {} as AppCheckService; - }, - ComponentType.PUBLIC - ) - ); - return app; -} - -export function getFakeCustomTokenProvider(): CustomProvider { - return new CustomProvider({ - getToken: () => - Promise.resolve({ - token: 'fake-custom-app-check-token', - expireTimeMillis: 1 - }) - }); -} - -export function getFakePlatformLoggingProvider( - fakeLogString: string = 'a/1.2.3 b/2.3.4' -): Provider<'platform-logger'> { - const container = new ComponentContainer('test'); - container.addComponent( - new Component( - 'platform-logger', - () => ({ getPlatformInfoString: () => fakeLogString }), - ComponentType.PRIVATE - ) - ); - - return container.getProvider('platform-logger'); -} - -export function getFakeGreCAPTCHA(): GreCAPTCHA { - return { - ready: callback => callback(), - render: (_container, _parameters) => 'fake_widget_1', - execute: (_siteKey, _options) => Promise.resolve('fake_recaptcha_token') - }; -} - -/** - * Returns all script tags in DOM matching our reCAPTCHA url pattern. - * Tests in other files may have inserted multiple reCAPTCHA scripts, because they don't - * care about it. - */ -export function findgreCAPTCHAScriptsOnPage(): HTMLScriptElement[] { - const scriptTags = window.document.getElementsByTagName('script'); - const tags = []; - for (const tag of Object.values(scriptTags)) { - if (tag.src && tag.src.includes(RECAPTCHA_URL)) { - tags.push(tag); - } - } - return tags; -} - -export function removegreCAPTCHAScriptsOnPage(): void { - const tags = findgreCAPTCHAScriptsOnPage(); - - for (const tag of tags) { - tag.remove(); - } - - if (self.grecaptcha) { - self.grecaptcha = undefined; - } -} diff --git a/packages-exp/app-check-exp/tsconfig.json b/packages-exp/app-check-exp/tsconfig.json deleted file mode 100644 index 356e7a53b8c..00000000000 --- a/packages-exp/app-check-exp/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "strict": true - }, - "exclude": ["dist/**/*"] -} diff --git a/packages-exp/app-compat/rollup.config.release.js b/packages-exp/app-compat/rollup.config.release.js deleted file mode 100644 index a8b90c16fae..00000000000 --- a/packages-exp/app-compat/rollup.config.release.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import json from '@rollup/plugin-json'; -import typescript from 'typescript'; -import pkg from './package.json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = [ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - }, - { - input: 'src/index.lite.ts', - output: { - file: pkg['lite-esm5'], - format: 'es', - sourcemap: true - }, - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - } -]; - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - }, - { - input: 'src/index.lite.ts', - output: { - file: pkg.lite, - format: 'es', - sourcemap: true - }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - } -]; - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-exp/karma.conf.js b/packages-exp/app-exp/karma.conf.js deleted file mode 100644 index c0917db53d1..00000000000 --- a/packages-exp/app-exp/karma.conf.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const karmaBase = require('../../config/karma.base'); - -const files = ['src/**/*.test.ts']; - -module.exports = function (config) { - const karmaConfig = Object.assign({}, karmaBase, { - // files to load into karma - files: files, - preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha'] - }); - - config.set(karmaConfig); -}; - -module.exports.files = files; diff --git a/packages-exp/app-exp/package.json b/packages-exp/app-exp/package.json deleted file mode 100644 index 0a1c4af4a2e..00000000000 --- a/packages-exp/app-exp/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "@firebase/app-exp", - "version": "0.0.900", - "private": true, - "description": "FirebaseApp", - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.cjs.js", - "browser": "dist/index.esm2017.js", - "module": "dist/index.esm2017.js", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", - "build:deps": "lerna run --scope @firebase/app-exp --include-dependencies build", - "dev": "rollup -c -w", - "test": "run-p lint test:all", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", - "test:all": "run-p test:browser test:node", - "test:browser": "karma start --single-run", - "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha src/**/*.test.ts --config ../../config/mocharc.node.js", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc", - "typings:public": "node ../../scripts/exp/use_typings.js ./dist/app-exp-public.d.ts", - "typings:internal": "node ../../scripts/exp/use_typings.js ./dist/app-exp.d.ts" - }, - "dependencies": { - "@firebase/util": "1.3.0", - "@firebase/logger": "0.2.6", - "@firebase/component": "0.5.6", - "tslib": "^2.1.0" - }, - "license": "Apache-2.0", - "devDependencies": { - "rollup": "2.52.2", - "@rollup/plugin-json": "4.1.0", - "rollup-plugin-replace": "2.2.0", - "rollup-plugin-typescript2": "0.30.0", - "typescript": "4.2.2" - }, - "repository": { - "directory": "packages-exp/app-exp", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "typings": "./dist/packages-exp/app-exp/src/index.d.ts", - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - }, - "esm5": "dist/index.esm5.js" -} \ No newline at end of file diff --git a/packages-exp/app-exp/rollup.config.release.js b/packages-exp/app-exp/rollup.config.release.js deleted file mode 100644 index ce92982b33d..00000000000 --- a/packages-exp/app-exp/rollup.config.release.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: false - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: false - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-exp/rollup.shared.js b/packages-exp/app-exp/rollup.shared.js deleted file mode 100644 index 32f7926601f..00000000000 --- a/packages-exp/app-exp/rollup.shared.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -export const es5BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - /** - * Node.js Build - */ - { - input: 'src/index.ts', - output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -export const es2017BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/app-exp/src/constants.ts b/packages-exp/app-exp/src/constants.ts deleted file mode 100644 index f00a93f0da9..00000000000 --- a/packages-exp/app-exp/src/constants.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { name as appName } from '../package.json'; -import { name as appCompatName } from '../../app-compat/package.json'; -import { name as analyticsCompatName } from '../../../packages-exp/analytics-compat/package.json'; -import { name as analyticsName } from '../../../packages-exp/analytics-exp/package.json'; -import { name as appCheckCompatName } from '../../../packages-exp/app-check-compat/package.json'; -import { name as appCheckName } from '../../../packages-exp/app-check-exp/package.json'; -import { name as authName } from '../../../packages-exp/auth-exp/package.json'; -import { name as authCompatName } from '../../../packages-exp/auth-compat-exp/package.json'; -import { name as databaseName } from '../../../packages/database/package.json'; -import { name as databaseCompatName } from '../../../packages/database/compat/package.json'; -import { name as functionsName } from '../../../packages-exp/functions-exp/package.json'; -import { name as functionsCompatName } from '../../../packages-exp/functions-compat/package.json'; -import { name as installationsName } from '../../../packages-exp/installations-exp/package.json'; -import { name as installationsCompatName } from '../../../packages-exp/installations-compat/package.json'; -import { name as messagingName } from '../../../packages-exp/messaging-exp/package.json'; -import { name as messagingCompatName } from '../../../packages-exp/messaging-compat/package.json'; -import { name as performanceName } from '../../../packages-exp/performance-exp/package.json'; -import { name as performanceCompatName } from '../../../packages-exp/performance-compat/package.json'; -import { name as remoteConfigName } from '../../../packages-exp/remote-config-exp/package.json'; -import { name as remoteConfigCompatName } from '../../../packages-exp/remote-config-compat/package.json'; -import { name as storageName } from '../../../packages/storage/package.json'; -import { name as storageCompatName } from '../../../packages/storage/compat/package.json'; -import { name as firestoreName } from '../../../packages/firestore/package.json'; -import { name as firestoreCompatName } from '../../../packages/firestore/compat/package.json'; -import { name as packageName } from '../../../packages-exp/firebase-exp/package.json'; - -/** - * The default app name - * - * @internal - */ -export const DEFAULT_ENTRY_NAME = '[DEFAULT]'; - -export const PLATFORM_LOG_STRING = { - [appName]: 'fire-core', - [appCompatName]: 'fire-core-compat', - [analyticsName]: 'fire-analytics', - [analyticsCompatName]: 'fire-analytics-compat', - [appCheckName]: 'fire-app-check', - [appCheckCompatName]: 'fire-app-check-compat', - [authName]: 'fire-auth', - [authCompatName]: 'fire-auth-compat', - [databaseName]: 'fire-rtdb', - [databaseCompatName]: 'fire-rtdb-compat', - [functionsName]: 'fire-fn', - [functionsCompatName]: 'fire-fn-compat', - [installationsName]: 'fire-iid', - [installationsCompatName]: 'fire-iid-compat', - [messagingName]: 'fire-fcm', - [messagingCompatName]: 'fire-fcm-compat', - [performanceName]: 'fire-perf', - [performanceCompatName]: 'fire-perf-compat', - [remoteConfigName]: 'fire-rc', - [remoteConfigCompatName]: 'fire-rc-compat', - [storageName]: 'fire-gcs', - [storageCompatName]: 'fire-gcs-compat', - [firestoreName]: 'fire-fst', - [firestoreCompatName]: 'fire-fst-compat', - 'fire-js': 'fire-js', // Platform identifier for JS SDK. - [packageName]: 'fire-js-all' -} as const; diff --git a/packages-exp/app-exp/src/errors.ts b/packages-exp/app-exp/src/errors.ts deleted file mode 100644 index b8bbae5c1b8..00000000000 --- a/packages-exp/app-exp/src/errors.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorFactory, ErrorMap } from '@firebase/util'; - -export const enum AppError { - NO_APP = 'no-app', - BAD_APP_NAME = 'bad-app-name', - DUPLICATE_APP = 'duplicate-app', - APP_DELETED = 'app-deleted', - INVALID_APP_ARGUMENT = 'invalid-app-argument', - INVALID_LOG_ARGUMENT = 'invalid-log-argument' -} - -const ERRORS: ErrorMap = { - [AppError.NO_APP]: - "No Firebase App '{$appName}' has been created - " + - 'call Firebase App.initializeApp()', - [AppError.BAD_APP_NAME]: "Illegal App name: '{$appName}", - [AppError.DUPLICATE_APP]: - "Firebase App named '{$appName}' already exists with different options or config", - [AppError.APP_DELETED]: "Firebase App named '{$appName}' already deleted", - [AppError.INVALID_APP_ARGUMENT]: - 'firebase.{$appName}() takes either no argument or a ' + - 'Firebase App instance.', - [AppError.INVALID_LOG_ARGUMENT]: - 'First argument to `onLog` must be null or a function.' -}; - -interface ErrorParams { - [AppError.NO_APP]: { appName: string }; - [AppError.BAD_APP_NAME]: { appName: string }; - [AppError.DUPLICATE_APP]: { appName: string }; - [AppError.APP_DELETED]: { appName: string }; - [AppError.INVALID_APP_ARGUMENT]: { appName: string }; -} - -export const ERROR_FACTORY = new ErrorFactory( - 'app', - 'Firebase', - ERRORS -); diff --git a/packages-exp/app-exp/src/firebaseApp.ts b/packages-exp/app-exp/src/firebaseApp.ts deleted file mode 100644 index 7333a76f22d..00000000000 --- a/packages-exp/app-exp/src/firebaseApp.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - FirebaseApp, - FirebaseOptions, - FirebaseAppSettings -} from './public-types'; -import { - ComponentContainer, - Component, - ComponentType -} from '@firebase/component'; -import { ERROR_FACTORY, AppError } from './errors'; - -export class FirebaseAppImpl implements FirebaseApp { - private readonly _options: FirebaseOptions; - private readonly _name: string; - /** - * Original config values passed in as a constructor parameter. - * It is only used to compare with another config object to support idempotent initializeApp(). - * - * Updating automaticDataCollectionEnabled on the App instance will not change its value in _config. - */ - private readonly _config: Required; - private _automaticDataCollectionEnabled: boolean; - private _isDeleted = false; - private readonly _container: ComponentContainer; - - constructor( - options: FirebaseOptions, - config: Required, - container: ComponentContainer - ) { - this._options = { ...options }; - this._config = { ...config }; - this._name = config.name; - this._automaticDataCollectionEnabled = - config.automaticDataCollectionEnabled; - this._container = container; - this.container.addComponent( - new Component('app-exp', () => this, ComponentType.PUBLIC) - ); - } - - get automaticDataCollectionEnabled(): boolean { - this.checkDestroyed(); - return this._automaticDataCollectionEnabled; - } - - set automaticDataCollectionEnabled(val: boolean) { - this.checkDestroyed(); - this._automaticDataCollectionEnabled = val; - } - - get name(): string { - this.checkDestroyed(); - return this._name; - } - - get options(): FirebaseOptions { - this.checkDestroyed(); - return this._options; - } - - get config(): Required { - this.checkDestroyed(); - return this._config; - } - - get container(): ComponentContainer { - return this._container; - } - - get isDeleted(): boolean { - return this._isDeleted; - } - - set isDeleted(val: boolean) { - this._isDeleted = val; - } - - /** - * This function will throw an Error if the App has already been deleted - - * use before performing API actions on the App. - */ - private checkDestroyed(): void { - if (this.isDeleted) { - throw ERROR_FACTORY.create(AppError.APP_DELETED, { appName: this._name }); - } - } -} diff --git a/packages-exp/app-exp/src/logger.ts b/packages-exp/app-exp/src/logger.ts deleted file mode 100644 index 6e38f59a3f8..00000000000 --- a/packages-exp/app-exp/src/logger.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Logger } from '@firebase/logger'; - -export const logger = new Logger('@firebase/app'); diff --git a/packages-exp/app-exp/src/platformLoggerService.ts b/packages-exp/app-exp/src/platformLoggerService.ts deleted file mode 100644 index bcb5c3900c2..00000000000 --- a/packages-exp/app-exp/src/platformLoggerService.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - ComponentContainer, - ComponentType, - Provider, - Name -} from '@firebase/component'; -import { PlatformLoggerService, VersionService } from './types'; - -export class PlatformLoggerServiceImpl implements PlatformLoggerService { - constructor(private readonly container: ComponentContainer) {} - // In initial implementation, this will be called by installations on - // auth token refresh, and installations will send this string. - getPlatformInfoString(): string { - const providers = this.container.getProviders(); - // Loop through providers and get library/version pairs from any that are - // version components. - return providers - .map(provider => { - if (isVersionServiceProvider(provider)) { - const service = provider.getImmediate() as VersionService; - return `${service.library}/${service.version}`; - } else { - return null; - } - }) - .filter(logString => logString) - .join(' '); - } -} -/** - * - * @param provider check if this provider provides a VersionService - * - * NOTE: Using Provider<'app-version'> is a hack to indicate that the provider - * provides VersionService. The provider is not necessarily a 'app-version' - * provider. - */ -function isVersionServiceProvider(provider: Provider): boolean { - const component = provider.getComponent(); - return component?.type === ComponentType.VERSION; -} diff --git a/packages-exp/app-exp/src/registerCoreComponents.ts b/packages-exp/app-exp/src/registerCoreComponents.ts deleted file mode 100644 index 51fae419102..00000000000 --- a/packages-exp/app-exp/src/registerCoreComponents.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Component, ComponentType } from '@firebase/component'; -import { PlatformLoggerServiceImpl } from './platformLoggerService'; -import { name, version } from '../package.json'; -import { _registerComponent } from './internal'; -import { registerVersion } from './api'; - -export function registerCoreComponents(variant?: string): void { - _registerComponent( - new Component( - 'platform-logger', - container => new PlatformLoggerServiceImpl(container), - ComponentType.PRIVATE - ) - ); - - // Register `app` package. - registerVersion(name, version, variant); - // Register platform SDK identifier (no version). - registerVersion('fire-js', ''); -} diff --git a/packages-exp/auth-compat-exp/demo/public/index.html b/packages-exp/auth-compat-exp/demo/public/index.html deleted file mode 100644 index 3d518d16854..00000000000 --- a/packages-exp/auth-compat-exp/demo/public/index.html +++ /dev/null @@ -1,767 +0,0 @@ - - - - - Headless App - - - - - - - - - - - - - - - - -

- - - - - - - -
-
- - -
-
- - -
-
- - - [anonymous] / - uid: - -
-
- - / - - - - - - - -
- - - -
-
-
-
- - -
- -
-
-
- - - -
-
- -
Development mode APIs
-
-
- - -
- -
- - -
Web Worker Testing
-
- -
- -
Service Worker Testing
-
- -
- - -
Auth State Persistence
-
- - -
- - -
Language code
-
- - - -
- - -
Sign Up
-
- - - -
- - - -
Sign In
-
- - - -
-
- - -
- -
- - - - - -
- -
- - - - - -
- - -
Sign In with Email Link
-
- - - -
-
- - -
- - -
Password Reset
-
- - -
-
- - - - -
- -
Fetch Sign In Methods
-
- - -
- - -
Update Current User
-
- -
-
- -
-
-
- -
Update Profile
-
- - -
-
- - -
-
- - - -
- - - -
Linking/Unlinking
- -
- - - -
-
- - - - - -
- -
- - - - - - - -
- -
- - - - - -
- -
- - -
- - -
Enroll Second Factor
- -
-
-
- - - - - - -
-
-
- - -
Other Actions
- -
- - -
- - - - - - - -
Delete account
- -
- -
-
Web
-
- -
-
Android
-
-
- -
- - -
- -
-
-
iOS
-
-
- -
-
-
-
- - -
- -
-
-
-

-              
-            
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages-exp/auth-compat-exp/demo/public/service-worker.js b/packages-exp/auth-compat-exp/demo/public/service-worker.js deleted file mode 100644 index c7c9a9ab49b..00000000000 --- a/packages-exp/auth-compat-exp/demo/public/service-worker.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Service worker for Firebase Auth test app application. The - * service worker caches all content and only serves cached content in offline - * mode. - */ - -importScripts('/dist/firebase-app.js'); -importScripts('/dist/firebase-auth.js'); -importScripts('config.js'); - -// Initialize the Firebase app in the web worker. -firebase.initializeApp(config); - -var CACHE_NAME = 'cache-v1'; -var urlsToCache = [ - '/', - '/manifest.json', - '/config.js', - '/script.js', - '/common.js', - '/style.css', - '/dist/firebase-app.js', - '/dist/firebase-auth.js', - '/dist/firebase-database.js' -]; - -/** - * Returns a promise that resolves with an ID token if available. - * @return {!Promise} The promise that resolves with an ID token if - * available. Otherwise, the promise resolves with null. - */ -var getIdToken = function () { - return new Promise(function (resolve, reject) { - firebase.auth().onAuthStateChanged(function (user) { - if (user) { - user.getIdToken().then( - function (idToken) { - resolve(idToken); - }, - function (error) { - resolve(null); - } - ); - } else { - resolve(null); - } - }); - }).catch(function (error) { - console.log(error); - }); -}; - -/** - * @param {string} url The URL whose origin is to be returned. - * @return {string} The origin corresponding to given URL. - */ -var getOriginFromUrl = function (url) { - // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript - var pathArray = url.split('/'); - var protocol = pathArray[0]; - var host = pathArray[2]; - return protocol + '//' + host; -}; - -self.addEventListener('install', function (event) { - // Perform install steps. - event.waitUntil( - caches.open(CACHE_NAME).then(function (cache) { - // Add all URLs of resources we want to cache. - return cache.addAll(urlsToCache).catch(function (error) { - // Suppress error as some of the files may not be available for the - // current page. - }); - }) - ); -}); - -// As this is a test app, let's only return cached data when offline. -self.addEventListener('fetch', function (event) { - var fetchEvent = event; - var requestProcessor = function (idToken) { - var req = event.request; - // For same origin https requests, append idToken to header. - if ( - self.location.origin == getOriginFromUrl(event.request.url) && - (self.location.protocol == 'https:' || - self.location.hostname == 'localhost') && - idToken - ) { - // Clone headers as request headers are immutable. - var headers = new Headers(); - for (var entry of req.headers.entries()) { - headers.append(entry[0], entry[1]); - } - // Add ID token to header. We can't add to Authentication header as it - // will break HTTP basic authentication. - headers.append('x-id-token', idToken); - try { - req = new Request(req.url, { - method: req.method, - headers: headers, - mode: 'same-origin', - credentials: req.credentials, - cache: req.cache, - redirect: req.redirect, - referrer: req.referrer, - body: req.body, - bodyUsed: req.bodyUsed, - context: req.context - }); - } catch (e) { - // This will fail for CORS requests. We just continue with the - // fetch caching logic below and do not pass the ID token. - } - } - return fetch(req) - .then(function (response) { - // Check if we received a valid response. - // If not, just funnel the error response. - if (!response || response.status !== 200 || response.type !== 'basic') { - return response; - } - // If response is valid, clone it and save it to the cache. - var responseToCache = response.clone(); - // Save response to cache. - caches.open(CACHE_NAME).then(function (cache) { - cache.put(fetchEvent.request, responseToCache); - }); - // After caching, return response. - return response; - }) - .catch(function (error) { - // For fetch errors, attempt to retrieve the resource from cache. - return caches.match(fetchEvent.request.clone()); - }) - .catch(function (error) { - // If error getting resource from cache, do nothing. - console.log(error); - }); - }; - // Try to fetch the resource first after checking for the ID token. - event.respondWith(getIdToken().then(requestProcessor, requestProcessor)); -}); - -self.addEventListener('activate', function (event) { - // Update this list with all caches that need to remain cached. - var cacheWhitelist = ['cache-v1']; - event.waitUntil( - caches.keys().then(function (cacheNames) { - return Promise.all( - cacheNames.map(function (cacheName) { - // Check if cache is not whitelisted above. - if (cacheWhitelist.indexOf(cacheName) === -1) { - // If not whitelisted, delete it. - return caches.delete(cacheName); - } - }) - ); - }) - ); -}); diff --git a/packages-exp/auth-compat-exp/demo/public/web-worker.js b/packages-exp/auth-compat-exp/demo/public/web-worker.js deleted file mode 100644 index 9ce860cf0dc..00000000000 --- a/packages-exp/auth-compat-exp/demo/public/web-worker.js +++ /dev/null @@ -1,210 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Web worker for Firebase Auth test app application. The - * web worker tries to run operations on the Auth instance for testing purposes. - */ - -importScripts('/dist/firebase-app.js'); -importScripts('/dist/firebase-auth.js'); -importScripts('config.js'); - -// Initialize the Firebase app in the web worker. -firebase.initializeApp(config); - -/** - * Returns a promise that resolves with an ID token if available. - * @return {!Promise} The promise that resolves with an ID token if - * available. Otherwise, the promise resolves with null. - */ -var getIdToken = function () { - return new Promise(function (resolve, reject) { - firebase.auth().onAuthStateChanged(function (user) { - if (user) { - user.getIdToken().then( - function (idToken) { - resolve(idToken); - }, - function (error) { - resolve(null); - } - ); - } else { - resolve(null); - } - }); - }).catch(function (error) { - console.log(error); - }); -}; - -/** - * Runs various Firebase Auth tests in a web worker environment and confirms the - * expected behavior. This is useful for manual testing in different browsers. - * @param {string} googleIdToken The Google ID token to sign in with. - * @return {!Promise} A promise that resolves when all tests run - * successfully. - */ -var runWorkerTests = function (googleIdToken) { - var inMemoryPersistence = firebase.auth.Auth.Persistence.NONE; - var expectedDisplayName = 'Test User'; - var oauthCredential = firebase.auth.GoogleAuthProvider.credential( - googleIdToken - ); - var provider = new firebase.auth.GoogleAuthProvider(); - var OPERATION_NOT_SUPPORTED_CODE = - 'auth/operation-not-supported-in-this-environment'; - var email = - 'user' + - Math.floor(Math.random() * 10000000000).toString() + - '@example.com'; - var pass = 'password'; - return firebase - .auth() - .setPersistence(inMemoryPersistence) - .then(function () { - firebase.auth().useDeviceLanguage(); - return firebase.auth().signInAnonymously(); - }) - .then(function (result) { - if (!result.user.uid) { - throw new Error('signInAnonymously unexpectedly failed!'); - } - return result.user.updateProfile({ displayName: expectedDisplayName }); - }) - .then(function () { - if (firebase.auth().currentUser.displayName != expectedDisplayName) { - throw new Error('Profile update failed!'); - } - return firebase.auth().currentUser.delete(); - }) - .then(function () { - if (firebase.auth().currentUser) { - throw new Error('currentUser.delete unexpectedly failed!'); - } - return firebase.auth().createUserWithEmailAndPassword(email, pass); - }) - .then(function (result) { - if (result.user.email != email) { - throw new Error('createUserWithEmailAndPassword unexpectedly failed!'); - } - return firebase.auth().fetchProvidersForEmail(email); - }) - .then(function (providers) { - if (providers.length == 0 || providers[0] != 'password') { - throw new Error('fetchProvidersForEmail failed!'); - } - return firebase.auth().signInWithEmailAndPassword(email, pass); - }) - .then(function (result) { - if (result.user.email != email) { - throw new Error('signInWithEmailAndPassword unexpectedly failed!'); - } - return result.user.delete(); - }) - .then(function () { - return firebase - .auth() - .signInWithPopup(provider) - .catch(function (error) { - if (error.code != OPERATION_NOT_SUPPORTED_CODE) { - throw error; - } - }); - }) - .then(function () { - return firebase - .auth() - .signInWithRedirect(provider) - .catch(function (error) { - if (error.code != OPERATION_NOT_SUPPORTED_CODE) { - throw error; - } - }); - }) - .then(function () { - return Promise.resolve() - .then(function () { - return new firebase.auth.RecaptchaVerifier('id'); - }) - .then(function () { - throw new Error( - 'RecaptchaVerifer instantiation succeeded unexpectedly!' - ); - }) - .catch(function (error) { - if (error.code != OPERATION_NOT_SUPPORTED_CODE) { - throw error; - } - }); - }) - .then(function () { - return firebase.auth().signInWithCredential(oauthCredential); - }) - .then(function (result) { - if ( - !result.user || - !result.user.uid || - !result.credential || - !result.additionalUserInfo - ) { - throw new Error('signInWithCredential unexpectedly failed!'); - } - return firebase.auth().signOut(); - }) - .then(function () { - if (firebase.auth().currentUser) { - throw new Error('signOut unexpectedly failed!'); - } - }); -}; - -/** - * Handles the incoming message from the main script. - * @param {!Object} e The message event received. - */ -self.onmessage = function (e) { - if (e.data && e.data.type) { - var result = { type: e.data.type }; - switch (e.data.type) { - case 'GET_USER_INFO': - getIdToken().then(function (idToken) { - result.idToken = idToken; - result.uid = - firebase.auth().currentUser && firebase.auth().currentUser.uid; - self.postMessage(result); - }); - break; - case 'RUN_TESTS': - runWorkerTests(e.data.googleIdToken) - .then(function () { - result.status = 'success'; - self.postMessage(result); - }) - .catch(function (error) { - result.status = 'failure'; - // DataCloneError when postMessaging in IE11 and 10. - result.error = error.code ? error : error.message; - self.postMessage(result); - }); - break; - default: - self.postMessage({}); - } - } -}; diff --git a/packages-exp/auth-compat-exp/rollup.config.js b/packages-exp/auth-compat-exp/rollup.config.js deleted file mode 100644 index 06596518856..00000000000 --- a/packages-exp/auth-compat-exp/rollup.config.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getAllBuilds } from './rollup.config.shared'; - -// eslint-disable-next-line import/no-default-export -export default getAllBuilds({}); diff --git a/packages-exp/auth-compat-exp/rollup.config.release.js b/packages-exp/auth-compat-exp/rollup.config.release.js deleted file mode 100644 index 03fbdc6434d..00000000000 --- a/packages-exp/auth-compat-exp/rollup.config.release.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { getAllBuilds } from './rollup.config.shared'; - -// eslint-disable-next-line import/no-default-export -export default getAllBuilds({ - clean: true, - abortOnError: false, - transformers: [importPathTransformer] -}); diff --git a/packages-exp/auth-compat-exp/rollup.config.shared.js b/packages-exp/auth-compat-exp/rollup.config.shared.js deleted file mode 100644 index 4374ef33122..00000000000 --- a/packages-exp/auth-compat-exp/rollup.config.shared.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import resolve from '@rollup/plugin-node-resolve'; -import { uglify } from 'rollup-plugin-uglify'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import pkg from './package.json'; - -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); - -/** - * Common plugins for all builds - */ -const commonPlugins = [json(), resolve()]; - -/** - * ES5 Builds - */ -export function getEs5Builds(additionalTypescriptPlugins = {}) { - const es5BuildPlugins = [ - ...commonPlugins, - typescriptPlugin({ - typescript, - ...additionalTypescriptPlugins - }) - ]; - - return [ - /** - * Browser Builds - */ - { - input: 'index.ts', - output: [{ file: pkg.esm5, format: 'esm', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - }, - /** - * Node.js Build - */ - { - input: 'index.node.ts', - output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: true - } - }, - /** - * UMD build - */ - { - input: `./index.ts`, - output: { - compact: true, - file: `dist/firebase-auth.js`, - format: 'umd', - sourcemap: true, - extend: true, - name: 'firebase', - globals: { - '@firebase/app-compat': 'firebase', - '@firebase/app': 'firebase.INTERNAL.modularAPIs' - }, - /** - * use iife to avoid below error in the old Safari browser - * SyntaxError: Functions cannot be declared in a nested block in strict mode - * https://github.com/firebase/firebase-js-sdk/issues/1228 - * - */ - intro: ` - try { - (function() {`, - outro: ` - }).apply(this, arguments); - } catch(err) { - console.error(err); - throw new Error( - 'Cannot instantiate firebase-auth.js - ' + - 'be sure to load firebase-app.js first.' - ); - }` - }, - plugins: [...es5BuildPlugins, uglify()], - external: ['@firebase/app-compat', '@firebase/app'] - } - ]; -} - -/** - * ES2017 Builds - */ -export function getEs2017Builds(additionalTypescriptPlugins = {}) { - const es2017BuildPlugins = [ - ...commonPlugins, - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - ...additionalTypescriptPlugins - }) - ]; - return [ - /** - * Browser Builds - */ - { - input: 'index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - } - ]; -} - -export function getAllBuilds(additionalTypescriptPlugins = {}) { - return [ - ...getEs5Builds(additionalTypescriptPlugins), - ...getEs2017Builds(additionalTypescriptPlugins) - ]; -} diff --git a/packages-exp/auth-exp/README.md b/packages-exp/auth-exp/README.md deleted file mode 100644 index 6d4cb154b94..00000000000 --- a/packages-exp/auth-exp/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# @firebase/auth-exp - -This is the Firebase Authentication component of the Firebase JS SDK. - -**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** - -## Testing - -The modular Auth SDK has both unit tests and integration tests, along with a -host of npm scripts to run these tests. The most important commands are: - -| Command | Description | -| ------- | ----------- | -| `yarn test` | This will run lint, unit tests, and integration tests against the live environment| -| `yarn test:` | Runs all browser tests, unit and integration | -| `yarn test::unit` | Runs only \ unit tests | -| `yarn test::unit:debug` | Runs \ unit tests, auto-watching for file system changes | -| `yarn test::integration` | Runs only integration tests against the live environment | -| `yarn test::integration:local` | Runs all headless \ integration tests against the emulator (more below) | - -Where \ is "browser" or "node". There are also cordova tests, but they -are not broken into such granular details. Check out `package.json` for more. - -### Integration testing with the emulator - -To test against the emulator, set up the Auth emulator -([instructions](https://firebase.google.com/docs/emulator-suite/connect_and_prototype)). -The easiest way to run these tests is to use the `firebase emulators:exec` -command -([documentation](https://firebase.google.com/docs/emulator-suite/install_and_configure#startup)). -You can also manually start the emulator separately, and then point the tests -to it by setting the `GCLOUD_PROJECT` and `FIREBASE_AUTH_EMULATOR_HOST` -environmental variables. In addition to the commands listed above, the below -commands also run various tests: - - * `yarn test:integration:local` — Executes Node and browser emulator - integration tests, as well as the Selenium WebDriver tests - - * `yarn test:webdriver` — Executes only the Selenium WebDriver - integration tests - -For example, to run all integration and WebDriver tests against the emulator, -you would simply execute the following command: - -```sh -firebase emulators:exec --project foo-bar --only auth "yarn test:integration:local" -``` - -### Selenium Webdriver tests - -These tests assume that you have both Firefox and Chrome installed on your -computer and in your `$PATH`. The tests will error out if this is not the case. -The WebDriver tests talk to the emulator, but unlike the headless integration -tests, these run in a browser robot environment; the assertions themselves run -in Node. When you run these tests a small Express server will be started to -serve the static files the browser robot uses. diff --git a/packages-exp/auth-exp/demo/.gitignore b/packages-exp/auth-exp/demo/.gitignore deleted file mode 100644 index 798f66cee47..00000000000 --- a/packages-exp/auth-exp/demo/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -src/config.js -.firebaserc -.firebase -public/service-worker.* -public/web-worker.* -public/index.js* \ No newline at end of file diff --git a/packages-exp/auth-exp/demo/README.md b/packages-exp/auth-exp/demo/README.md deleted file mode 100644 index 6e62f12fa01..00000000000 --- a/packages-exp/auth-exp/demo/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Firebase-Auth for web - Auth Demo (Auth Next) - -## Prerequisite - -You need to have created a Firebase Project in the -[Firebase Console](https://firebase.google.com/console/) as well as configured a web app. - -## Installation -Make sure you run `yarn` to install all dependencies in the root directory. - -Enable the Auth providers you would like to offer your users in the console, under -Auth > Sign-in methods. - -Run: - -```bash -git clone https://github.com/firebase/firebase-js-sdk.git -cd firebase-js-sdk/packages-exp/auth-exp/demo -``` - -This will clone the repository in the current directory. - -If you want to be able to deploy the demo app to one of your own Firebase Hosting instance, -configure it using the following command: - -```bash -firebase use --add -``` - -Select the project you have created in the prerequisite, and type in `default` or -any other name as the alias to use for this project. - -Copy `src/sample-config.js` to `src/config.js`: - -```bash -cp src/sample-config.js src/config.js -``` - -Then copy and paste the Web snippet config found in the console (either by clicking "Add Firebase to -your web app" button in your Project overview, or clicking the "Web setup" button in the Auth page) -in the `config.js` file. - -## Deploy - -Before deploying, you may need to build the auth-exp package: -```bash -yarn build:deps -``` - -This can take some time, and you only need to do it if you've modified the auth-exp package. - -To run the app locally, simply issue the following command in the `auth-exp/demo` directory: - -```bash -yarn run demo -``` - -This will compile all the files needed to run Firebase Auth, and start a Firebase server locally at -[http://localhost:5000](http://localhost:5000). - diff --git a/packages-exp/auth-exp/demo/database.rules.json b/packages-exp/auth-exp/demo/database.rules.json deleted file mode 100644 index 03292687cf2..00000000000 --- a/packages-exp/auth-exp/demo/database.rules.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rules": { - ".read": "auth != null", - ".write": "auth != null", - "users": { - "$user_id": { - ".read": "$user_id === auth.uid", - ".write": "$user_id === auth.uid" - } - } - } -} - diff --git a/packages-exp/auth-exp/demo/firebase.json b/packages-exp/auth-exp/demo/firebase.json deleted file mode 100644 index 300ada0abc9..00000000000 --- a/packages-exp/auth-exp/demo/firebase.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "database": { - "rules": "database.rules.json" - }, - "hosting": { - "public": "public", - "rewrites": [ - { - "source": "/checkIfAuthenticated", - "function": "checkIfAuthenticated" - } - ] - } -} diff --git a/packages-exp/auth-exp/demo/functions/index.js b/packages-exp/auth-exp/demo/functions/index.js deleted file mode 100644 index f3a9091bf20..00000000000 --- a/packages-exp/auth-exp/demo/functions/index.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the HTTP endpoints hosted via firebase functions which - * are used to test service worker functionality for Firebase Auth via demo - * app. - */ - -const functions = require('firebase-functions'); -const admin = require('firebase-admin'); - -admin.initializeApp(functions.config().firebase); - -exports.checkIfAuthenticated = functions.https.onRequest((req, res) => { - const idToken = req.get('x-id-token'); - res.setHeader('Content-Type', 'application/json'); - if (idToken) { - admin - .auth() - .verifyIdToken(idToken) - .then(decodedIdToken => { - res.status(200).send(JSON.stringify({ uid: decodedIdToken.sub })); - }) - .catch(error => { - res.status(400).send(JSON.stringify({ error: error.code })); - }); - } else { - res.status(403).send(JSON.stringify({ error: 'Unauthorized access' })); - } -}); diff --git a/packages-exp/auth-exp/demo/functions/package.json b/packages-exp/auth-exp/demo/functions/package.json deleted file mode 100644 index c825a2e5203..00000000000 --- a/packages-exp/auth-exp/demo/functions/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "functions", - "description": "Cloud Functions for Firebase", - "scripts": { - "serve": "firebase serve --only functions", - "shell": "firebase experimental:functions:shell", - "start": "yarn shell", - "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log" - }, - "dependencies": { - "firebase-admin": "8.13.0", - "firebase-functions": "3.14.1" - }, - "private": true, - "engines": { - "node": "10" - } -} diff --git a/packages-exp/auth-exp/demo/functions/yarn.lock b/packages-exp/auth-exp/demo/functions/yarn.lock deleted file mode 100644 index d0de26b87a5..00000000000 --- a/packages-exp/auth-exp/demo/functions/yarn.lock +++ /dev/null @@ -1,1759 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@firebase/app-types@0.6.1": - version "0.6.1" - resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz#dcbd23030a71c0c74fc95d4a3f75ba81653850e9" - integrity sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg== - -"@firebase/auth-interop-types@0.1.5": - version "0.1.5" - resolved "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz#9fc9bd7c879f16b8d1bb08373a0f48c3a8b74557" - integrity sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw== - -"@firebase/component@0.1.19": - version "0.1.19" - resolved "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz#bd2ac601652c22576b574c08c40da245933dbac7" - integrity sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ== - dependencies: - "@firebase/util" "0.3.2" - tslib "^1.11.1" - -"@firebase/database-types@0.5.2": - version "0.5.2" - resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz#23bec8477f84f519727f165c687761e29958b63c" - integrity sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g== - dependencies: - "@firebase/app-types" "0.6.1" - -"@firebase/database@^0.6.0": - version "0.6.13" - resolved "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz#b96fe0c53757dd6404ee085fdcb45c0f9f525c17" - integrity sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA== - dependencies: - "@firebase/auth-interop-types" "0.1.5" - "@firebase/component" "0.1.19" - "@firebase/database-types" "0.5.2" - "@firebase/logger" "0.2.6" - "@firebase/util" "0.3.2" - faye-websocket "0.11.3" - tslib "^1.11.1" - -"@firebase/logger@0.2.6": - version "0.2.6" - resolved "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz#3aa2ca4fe10327cabf7808bd3994e88db26d7989" - integrity sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw== - -"@firebase/util@0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz#87de27f9cffc2324651cabf6ec133d0a9eb21b52" - integrity sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g== - dependencies: - tslib "^1.11.1" - -"@google-cloud/common@^2.1.1": - version "2.4.0" - resolved "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz#2783b7de8435024a31453510f2dab5a6a91a4c82" - integrity sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg== - dependencies: - "@google-cloud/projectify" "^1.0.0" - "@google-cloud/promisify" "^1.0.0" - arrify "^2.0.0" - duplexify "^3.6.0" - ent "^2.2.0" - extend "^3.0.2" - google-auth-library "^5.5.0" - retry-request "^4.0.0" - teeny-request "^6.0.0" - -"@google-cloud/firestore@^3.0.0": - version "3.8.6" - resolved "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.8.6.tgz#9e6dea57323a5824563430a759244825fb01d834" - integrity sha512-ox80NbrM1MLJgvAAUd1quFLx/ie/nSjrk1PtscSicpoYDlKb9e6j7pHrVpbopBMyliyfNl3tLJWaDh+x+uCXqw== - dependencies: - deep-equal "^2.0.0" - functional-red-black-tree "^1.0.1" - google-gax "^1.15.3" - readable-stream "^3.4.0" - through2 "^3.0.0" - -"@google-cloud/paginator@^2.0.0": - version "2.0.3" - resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz#c7987ad05d1c3ebcef554381be80e9e8da4e4882" - integrity sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg== - dependencies: - arrify "^2.0.0" - extend "^3.0.2" - -"@google-cloud/projectify@^1.0.0": - version "1.0.4" - resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz#28daabebba6579ed998edcadf1a8f3be17f3b5f0" - integrity sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg== - -"@google-cloud/promisify@^1.0.0": - version "1.0.4" - resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz#ce86ffa94f9cfafa2e68f7b3e4a7fad194189723" - integrity sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ== - -"@google-cloud/storage@^4.1.2": - version "4.7.0" - resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz#a7466086a83911c7979cc238d00a127ffb645615" - integrity sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ== - dependencies: - "@google-cloud/common" "^2.1.1" - "@google-cloud/paginator" "^2.0.0" - "@google-cloud/promisify" "^1.0.0" - arrify "^2.0.0" - compressible "^2.0.12" - concat-stream "^2.0.0" - date-and-time "^0.13.0" - duplexify "^3.5.0" - extend "^3.0.2" - gaxios "^3.0.0" - gcs-resumable-upload "^2.2.4" - hash-stream-validation "^0.2.2" - mime "^2.2.0" - mime-types "^2.0.8" - onetime "^5.1.0" - p-limit "^2.2.0" - pumpify "^2.0.0" - readable-stream "^3.4.0" - snakeize "^0.1.0" - stream-events "^1.0.1" - through2 "^3.0.0" - xdg-basedir "^4.0.0" - -"@grpc/grpc-js@~1.0.3": - version "1.0.5" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz#09948c0810e62828fdd61455b2eb13d7879888b0" - integrity sha512-Hm+xOiqAhcpT9RYM8lc15dbQD7aQurM7ZU8ulmulepiPlN7iwBXXwP3vSBUimoFoApRqz7pSIisXU8pZaCB4og== - dependencies: - semver "^6.2.0" - -"@grpc/proto-loader@^0.5.1": - version "0.5.6" - resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz#1dea4b8a6412b05e2d58514d507137b63a52a98d" - integrity sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ== - dependencies: - lodash.camelcase "^4.3.0" - protobufjs "^6.8.6" - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@types/body-parser@*": - version "1.19.0" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" - integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.34" - resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" - integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@*": - version "4.17.18" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" - integrity sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@4.17.3": - version "4.17.3" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9" - integrity sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "*" - "@types/serve-static" "*" - -"@types/fs-extra@^8.0.1": - version "8.1.1" - resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" - integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== - dependencies: - "@types/node" "*" - -"@types/long@^4.0.0", "@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== - -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - -"@types/node@*": - version "14.14.33" - resolved "https://registry.npmjs.org/@types/node/-/node-14.14.33.tgz#9e4f8c64345522e4e8ce77b334a8aaa64e2b6c78" - integrity sha512-oJqcTrgPUF29oUP8AsUqbXGJNuPutsetaa9kTQAQce5Lx5dTYWV02ScBiT/k1BX/Z7pKeqedmvp39Wu4zR7N7g== - -"@types/node@^13.7.0": - version "13.13.46" - resolved "https://registry.npmjs.org/@types/node/-/node-13.13.46.tgz#5471e176f3fa15e018dea7992072bf8ca208a3a6" - integrity sha512-dqpbzK/KDsOlEt+oyB3rv+u1IxlLFziZu/Z0adfRKoelkr+sTd6QcgiQC+HWq/vkYkHwG5ot2LxgV05aAjnhcg== - -"@types/node@^8.10.59": - version "8.10.66" - resolved "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" - integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== - -"@types/qs@*": - version "6.9.6" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" - integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== - -"@types/range-parser@*": - version "1.2.3" - resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" - integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== - -"@types/serve-static@*": - version "1.13.9" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" - integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -agent-base@6: - version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -arrify@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== - -available-typed-arrays@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" - integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== - dependencies: - array-filter "^1.0.0" - -base64-js@^1.3.0: - version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bignumber.js@^9.0.0: - version "9.0.1" - resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" - integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== - -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -compressible@^2.0.12: - version "2.0.18" - resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -concat-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" - integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.0.2" - typedarray "^0.0.6" - -configstore@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - 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" - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -date-and-time@^0.13.0: - version "0.13.1" - resolved "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz#d12ba07ac840d5b112dc4c83f8a03e8a51f78dd6" - integrity sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.1: - version "4.3.1" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - -deep-equal@^2.0.0: - version "2.0.5" - resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" - integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw== - dependencies: - call-bind "^1.0.0" - es-get-iterator "^1.1.1" - get-intrinsic "^1.0.1" - is-arguments "^1.0.4" - is-date-object "^1.0.2" - is-regex "^1.1.1" - isarray "^2.0.5" - object-is "^1.1.4" - object-keys "^1.1.1" - object.assign "^4.1.2" - regexp.prototype.flags "^1.3.0" - side-channel "^1.0.3" - which-boxed-primitive "^1.0.1" - which-collection "^1.0.1" - which-typed-array "^1.1.2" - -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -dicer@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" - integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== - dependencies: - streamsearch "0.1.2" - -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -duplexify@^3.5.0, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -duplexify@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz#7027dc374f157b122a8ae08c2d3ea4d2d953aa61" - integrity sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA== - dependencies: - end-of-stream "^1.4.1" - inherits "^2.0.3" - readable-stream "^3.1.1" - stream-shift "^1.0.0" - -ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: - version "1.0.11" - resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -ent@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= - -es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: - version "1.18.0" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" - integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - get-intrinsic "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.2" - is-callable "^1.2.3" - is-negative-zero "^2.0.1" - is-regex "^1.1.2" - is-string "^1.0.5" - object-inspect "^1.9.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.0" - -es-get-iterator@^1.1.1: - version "1.1.2" - resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" - integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.0" - has-symbols "^1.0.1" - is-arguments "^1.1.0" - is-map "^2.0.2" - is-set "^2.0.2" - is-string "^1.0.5" - isarray "^2.0.5" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -express@^4.17.1: - version "4.17.1" - resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-text-encoding@^1.0.0: - version "1.0.3" - resolved "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" - integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== - -faye-websocket@0.11.3: - version "0.11.3" - resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== - dependencies: - websocket-driver ">=0.5.1" - -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -firebase-admin@8.13.0: - version "8.13.0" - resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.13.0.tgz#997d34ae8357d7dc162ba622148bbebcf7f2e923" - integrity sha512-krXj5ncWMJBhCpXSn9UFY6zmDWjFjqgx+1e9ATXKFYndEjmKtNBuJzqdrAdDh7aTUR7X6+0TPx4Hbc08kd0lwQ== - dependencies: - "@firebase/database" "^0.6.0" - "@types/node" "^8.10.59" - dicer "^0.3.0" - jsonwebtoken "^8.5.1" - node-forge "^0.7.6" - optionalDependencies: - "@google-cloud/firestore" "^3.0.0" - "@google-cloud/storage" "^4.1.2" - -firebase-functions@3.14.1: - version "3.14.1" - resolved "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.14.1.tgz#3ac5bc70989365874f41d06bca3b42a233dd6039" - integrity sha512-hL/qm+i5i1qKYmAFMlQ4mwRngDkP+3YT3F4E4Nd5Hj2QKeawBdZiMGgEt6zqTx08Zq04vHiSnSM0z75UJRSg6Q== - dependencies: - "@types/express" "4.17.3" - cors "^2.8.5" - express "^4.17.1" - lodash "^4.17.14" - -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= - -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -gaxios@^2.0.0, gaxios@^2.1.0: - version "2.3.4" - resolved "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz#eea99353f341c270c5f3c29fc46b8ead56f0a173" - integrity sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA== - dependencies: - abort-controller "^3.0.0" - extend "^3.0.2" - https-proxy-agent "^5.0.0" - is-stream "^2.0.0" - node-fetch "^2.3.0" - -gaxios@^3.0.0: - version "3.2.0" - resolved "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz#11b6f0e8fb08d94a10d4d58b044ad3bec6dd486a" - integrity sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q== - dependencies: - abort-controller "^3.0.0" - extend "^3.0.2" - https-proxy-agent "^5.0.0" - is-stream "^2.0.0" - node-fetch "^2.3.0" - -gcp-metadata@^3.4.0: - version "3.5.0" - resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz#6d28343f65a6bbf8449886a0c0e4a71c77577055" - integrity sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA== - dependencies: - gaxios "^2.1.0" - json-bigint "^0.3.0" - -gcs-resumable-upload@^2.2.4: - version "2.3.3" - resolved "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz#02c616ed17eff6676e789910aeab3907d412c5f8" - integrity sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q== - dependencies: - abort-controller "^3.0.0" - configstore "^5.0.0" - gaxios "^2.0.0" - google-auth-library "^5.0.0" - pumpify "^2.0.0" - stream-events "^1.0.4" - -get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -google-auth-library@^5.0.0, google-auth-library@^5.5.0: - version "5.10.1" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz#504ec75487ad140e68dd577c21affa363c87ddff" - integrity sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg== - dependencies: - arrify "^2.0.0" - base64-js "^1.3.0" - ecdsa-sig-formatter "^1.0.11" - fast-text-encoding "^1.0.0" - gaxios "^2.1.0" - gcp-metadata "^3.4.0" - gtoken "^4.1.0" - jws "^4.0.0" - lru-cache "^5.0.0" - -google-gax@^1.15.3: - version "1.15.3" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz#e88cdcbbd19c7d88cc5fd7d7b932c4d1979a5aca" - integrity sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ== - dependencies: - "@grpc/grpc-js" "~1.0.3" - "@grpc/proto-loader" "^0.5.1" - "@types/fs-extra" "^8.0.1" - "@types/long" "^4.0.0" - abort-controller "^3.0.0" - duplexify "^3.6.0" - google-auth-library "^5.0.0" - is-stream-ended "^0.1.4" - lodash.at "^4.6.0" - lodash.has "^4.5.2" - node-fetch "^2.6.0" - protobufjs "^6.8.9" - retry-request "^4.0.0" - semver "^6.0.0" - walkdir "^0.4.0" - -google-p12-pem@^2.0.0: - version "2.0.4" - resolved "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz#036462394e266472632a78b685f0cc3df4ef337b" - integrity sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg== - dependencies: - node-forge "^0.9.0" - -graceful-fs@^4.1.2: - version "4.2.6" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== - -gtoken@^4.1.0: - version "4.1.4" - resolved "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz#925ff1e7df3aaada06611d30ea2d2abf60fcd6a7" - integrity sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA== - dependencies: - gaxios "^2.1.0" - google-p12-pem "^2.0.0" - jws "^4.0.0" - mime "^2.2.0" - -has-bigints@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== - -has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-stream-validation@^0.2.2: - version "0.2.4" - resolved "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz#ee68b41bf822f7f44db1142ec28ba9ee7ccb7512" - integrity sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ== - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-parser-js@>=0.5.1: - version "0.5.3" - resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" - integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== - -http-proxy-agent@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== - dependencies: - agent-base "6" - debug "4" - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-arguments@^1.0.4, is-arguments@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" - integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== - dependencies: - call-bind "^1.0.0" - -is-bigint@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" - integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== - -is-boolean-object@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" - integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== - dependencies: - call-bind "^1.0.0" - -is-callable@^1.1.4, is-callable@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== - -is-date-object@^1.0.1, is-date-object@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-map@^2.0.1, is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== - -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== - -is-number-object@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-regex@^1.1.1, is-regex@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" - integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== - dependencies: - call-bind "^1.0.2" - has-symbols "^1.0.1" - -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== - -is-stream-ended@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz#f50224e95e06bce0e356d440a4827cd35b267eda" - integrity sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw== - -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-typed-array@^1.1.3: - version "1.1.5" - resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" - integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== - dependencies: - available-typed-arrays "^1.0.2" - call-bind "^1.0.2" - es-abstract "^1.18.0-next.2" - foreach "^2.0.5" - has-symbols "^1.0.1" - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakset@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" - integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -json-bigint@^0.3.0: - version "0.3.1" - resolved "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.1.tgz#0c1729d679f580d550899d6a2226c228564afe60" - integrity sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ== - dependencies: - bignumber.js "^9.0.0" - -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - 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" - -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - 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" - -jwa@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" - integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - -jws@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" - integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== - dependencies: - jwa "^2.0.0" - safe-buffer "^5.0.1" - -lodash.at@^4.6.0: - version "4.6.0" - resolved "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz#93cdce664f0a1994ea33dd7cd40e23afd11b0ff8" - integrity sha1-k83OZk8KGZTqM9181A4jr9EbD/g= - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - -lodash.has@^4.5.2: - version "4.5.2" - resolved "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= - -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= - -lodash@^4.17.14: - version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -long@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -lru-cache@^5.0.0: - version "5.1.1" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -mime-db@1.46.0, "mime-db@>= 1.43.0 < 2": - version "1.46.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" - integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== - -mime-types@^2.0.8, mime-types@~2.1.24: - version "2.1.29" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" - integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== - dependencies: - mime-db "1.46.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.2.0: - version "2.5.2" - resolved "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" - integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== - -node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0: - version "2.6.1" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - -node-forge@^0.7.6: - version "0.7.6" - resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" - integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== - -node-forge@^0.9.0: - version "0.9.2" - resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz#b35a44c28889b2ea55cabf8c79e3563f9676190a" - integrity sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw== - -object-assign@^4: - version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-inspect@^1.9.0: - version "1.9.0" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" - integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== - -object-is@^1.1.4: - version "1.1.5" - resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -protobufjs@^6.8.6, protobufjs@^6.8.9: - version "6.10.2" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" - integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ== - 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" - -proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz#abfc7b5a621307c728b551decbbefb51f0e4aa1e" - integrity sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw== - dependencies: - duplexify "^4.1.1" - inherits "^2.0.3" - pump "^3.0.0" - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^2.0.0: - version "2.3.7" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -regexp.prototype.flags@^1.3.0: - version "1.3.1" - resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" - integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -retry-request@^4.0.0: - version "4.1.3" - resolved "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz#d5f74daf261372cff58d08b0a1979b4d7cab0fde" - integrity sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ== - dependencies: - debug "^4.1.1" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^5.6.0: - version "5.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0, semver@^6.2.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -send@0.17.1: - version "0.17.1" - resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - -side-channel@^1.0.3: - version "1.0.4" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -snakeize@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz#10c088d8b58eb076b3229bb5a04e232ce126422d" - integrity sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0= - -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -stream-events@^1.0.1, stream-events@^1.0.4, stream-events@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" - integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== - dependencies: - stubs "^3.0.0" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -streamsearch@0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= - -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stubs@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" - integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls= - -teeny-request@^6.0.0: - version "6.0.3" - resolved "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz#b617f9d5b7ba95c76a3f257f6ba2342b70228b1f" - integrity sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw== - dependencies: - http-proxy-agent "^4.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.2.0" - stream-events "^1.0.5" - uuid "^7.0.0" - -through2@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" - integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ== - dependencies: - inherits "^2.0.4" - readable-stream "2 || 3" - -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - -tslib@^1.11.1: - version "1.14.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -unbox-primitive@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f" - integrity sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA== - dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.0" - has-symbols "^1.0.0" - which-boxed-primitive "^1.0.1" - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -uuid@^7.0.0: - version "7.0.3" - resolved "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" - integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -walkdir@^0.4.0: - version "0.4.1" - resolved "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" - integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== - -websocket-driver@>=0.5.1: - version "0.7.4" - resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which-boxed-primitive@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - -which-typed-array@^1.1.2: - version "1.1.4" - resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" - integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== - dependencies: - available-typed-arrays "^1.0.2" - call-bind "^1.0.0" - es-abstract "^1.18.0-next.1" - foreach "^2.0.5" - function-bind "^1.1.1" - has-symbols "^1.0.1" - is-typed-array "^1.1.3" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/packages-exp/auth-exp/demo/public/common.js b/packages-exp/auth-exp/demo/public/common.js deleted file mode 100644 index 9224926af95..00000000000 --- a/packages-exp/auth-exp/demo/public/common.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Utilities for Auth test app features. - */ - -/** - * Initializes the widget for toggling reCAPTCHA size. - * @param {function(string):void} callback The callback to call when the - * size toggler is changed, which takes in the new reCAPTCHA size. - */ -function initRecaptchaToggle(callback) { - // Listen to recaptcha config togglers. - var $recaptchaConfigTogglers = $('.toggleRecaptcha'); - $recaptchaConfigTogglers.click(function (e) { - // Remove currently active option. - $recaptchaConfigTogglers.removeClass('active'); - // Set currently selected option. - $(this).addClass('active'); - // Get the current reCAPTCHA setting label. - var size = $(e.target).text().toLowerCase(); - callback(size); - }); -} - -// Install servicerWorker if supported. -if ('serviceWorker' in navigator) { - navigator.serviceWorker - .register('/service-worker.js', { scope: '/' }) - .then(function (reg) { - // Registration worked. - console.log('Registration succeeded. Scope is ' + reg.scope); - }) - .catch(function (error) { - // Registration failed. - console.log('Registration failed with ' + error.message); - }); -} - -var webWorker = null; -if (window.Worker) { - webWorker = new Worker('/web-worker.js'); - /** - * Handles the incoming message from the web worker. - * @param {!Object} e The message event received. - */ - webWorker.onmessage = function (e) { - console.log('User data passed through web worker: ', e.data); - switch (e.data.type) { - case 'GET_USER_INFO': - alertSuccess( - 'User data passed through web worker: ' + JSON.stringify(e.data) - ); - break; - case 'RUN_TESTS': - if (e.data.status == 'success') { - alertSuccess('Web worker tests ran successfully!'); - } else { - alertError('Error: ' + JSON.stringify(e.data.error)); - } - break; - default: - return; - } - }; -} - -/** - * Asks the web worker, if supported in current browser, to return the user info - * corresponding to the currentUser as seen within the worker. - */ -function onGetCurrentUserDataFromWebWorker() { - if (webWorker) { - webWorker.postMessage({ type: 'GET_USER_INFO' }); - } else { - alertError('Error: Web workers are not supported in the current browser!'); - } -} - -/** - * Runs various Firebase Auth tests in a web worker environment and confirms the - * expected behavior. This is useful for manual testing in different browsers. - * @param {string} googleIdToken The Google ID token to sign in with. - */ -function runWebWorkerTests(googleIdToken) { - if (webWorker) { - webWorker.postMessage({ - type: 'RUN_TESTS', - googleIdToken: googleIdToken - }); - } else { - alertError('Error: Web workers are not supported in the current browser!'); - } -} diff --git a/packages-exp/auth-exp/demo/public/index.html b/packages-exp/auth-exp/demo/public/index.html deleted file mode 100644 index aa32e144ed5..00000000000 --- a/packages-exp/auth-exp/demo/public/index.html +++ /dev/null @@ -1,763 +0,0 @@ - - - - - Headless App - - - - - - - - - - - -
- - - - - - - -
-
- - -
-
- - -
-
- - - [anonymous] / - uid: - -
-
- - / - - - - - - - -
- - - -
-
-
-
- - -
- -
-
-
- - - -
-
- -
Development mode APIs
-
-
- - -
- -
- - -
Web Worker Testing
-
- -
- -
Service Worker Testing
-
- -
- - -
Auth State Persistence
-
- - -
- - -
Language code
-
- - - -
- - -
Sign Up
-
- - - -
- - - -
Sign In
-
- - - -
-
- - -
- -
- - - - - -
- -
- - - - - -
- - -
Sign In with Email Link
-
- - - -
-
- - -
- - -
Password Reset
-
- - -
-
- - - - -
- -
Fetch Sign In Methods
-
- - -
- - -
Update Current User
-
- -
-
- -
-
-
- -
Update Profile
-
- - -
-
- - -
-
- - - -
- - - -
Linking/Unlinking
- -
- - - -
-
- - - - - -
- -
- - - - - - - -
- -
- - - - - -
- -
- - -
- - -
Enroll Second Factor
- -
-
-
- - - - - - -
-
-
- - -
Other Actions
- -
- - -
- - - - - - - -
Delete account
- -
- -
-
Web
-
- -
-
Android
-
-
- -
- - -
- -
-
-
iOS
-
-
- -
-
-
-
- - -
- -
-
-
-

-              
-            
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages-exp/auth-exp/demo/public/manifest.json b/packages-exp/auth-exp/demo/public/manifest.json deleted file mode 100644 index 646c61a9320..00000000000 --- a/packages-exp/auth-exp/demo/public/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "Firebse Auth Test App", - "short_name": "FirebaseAuthTest", - "start_url": "/", - "display": "standalone", - "background_color": "#fff", - "lang": "en-US", - "description": "Test app to test all functionality for Firebase Auth.", - "prefer_related_applications": false, - "theme_color": "#fff", - "scope": "/", - "orientation": "portrait-primary" -} diff --git a/packages-exp/auth-exp/demo/public/style.css b/packages-exp/auth-exp/demo/public/style.css deleted file mode 100644 index cdd999f8e8b..00000000000 --- a/packages-exp/auth-exp/demo/public/style.css +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -body { - padding-top: 70px; -} -body.user-info-displayed { - padding-top: 120px; -} - -@media (min-width: 768px) { - body { - padding-bottom: 100px; - } -} -@media (max-width: 767px) { - body { - padding-bottom: 15px; - } -} - -#tab-menu { - margin-bottom: 15px; -} - -#user-info { - display: none; - padding: 10px 15px; - position: fixed; - top: 50px; - width: 100%; - z-index: 1000; -} - -#toggle-user-placeholder { - margin-bottom: 15px; -} - -#user-info.current-user, -#toggle-user-placeholder .current-user, -#toggle-user label.active.current-user { - background-color: #d6e9c6; - border: 1px solid #dff0d8; - color: #3c763d; -} - -#user-info.last-user, -#toggle-user label.active.last-user { - background-color: #d9edf7; - border: 1px solid #bce8f1; - color: #31708f; -} - -#recaptcha-container { - border: 5px solid red; - bottom: 0; - position: fixed; - right: 0; - z-index: 1500; -} - -#recaptcha-container:empty { - border: none; -} - -.logs { - color: #555; - font-family: 'Courier New', Courier; - font-size: 0.9em; - word-wrap: break-word; -} - -.logs > .error { - color: #d9534f; -} - -/* Margin top for small screens when the logs are below the buttons */ -@media (max-width: 767px) { - .logs { - margin-top: 20px; - } -} - -.overlaying-alert { - bottom: 15px; - pointer-events: none; - position: fixed; - width: 100%; - word-wrap: break-word; - z-index: 1010; -} - -.actions { - margin-bottom: 15px; -} - -.group { - border-top: 1px solid #555; - color: #555; - font-size: 0.9em; - font-weight: bold; - letter-spacing: 0.2em; - margin-bottom: 15px; - padding: 2px 0 0 10px; -} - -button + .group, -div + .group, -.btn-block + .group, -.form + .group { - margin-top: 30px; -} - -.form { - text-align: center; -} - -.form-bordered { - border: 1px solid #CCC; - border-radius: 9px; - padding: 5px; -} - -button + .form, -input + .form, -.form + .form { - margin: 15px 0; -} - -.form + button, -.form-control + .btn-block, -.form-control + .form-control { - margin-top: 5px; -} - -/* Bootstrap .hidden adds the !important which invalides jQuery .show() */ -.hidden, -.hide, -.profile, -.overlaying-alert > .alert, -#toggle-user { - display: none; -} - -.profile { - line-height: 30px; -} - -@media (max-width: 767px) { - .profile { - /* Use a smaller line height for small screens so user information doesn't - take up half the screen. */ - line-height: normal; - } -} - -.profile-uid { - font-family: 'Courier New', Courier; -} - -.profile-image { - float: left; - height: 30px; - margin-right: 10px; -} - -.profile-email-not-verified { - color: #d9534f; -} - -.profile-providers { - color: #333; -} - -.profile-providers > i { - margin: 0 5px; -} - -.radio-block { - margin-bottom: 15px; - width: 100%; -} - -.radio-block > label { - width: 50%; -} - -/** Overrides default drop down menu styles for enrolled factors display. */ -.enrolled-second-factors { - left: initial; - min-width: 400px; - right: 0px; - width: 100%; -} diff --git a/packages-exp/auth-exp/package.json b/packages-exp/auth-exp/package.json deleted file mode 100644 index 1fc5b68c13a..00000000000 --- a/packages-exp/auth-exp/package.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "name": "@firebase/auth-exp", - "version": "0.0.900", - "private": true, - "description": "The Firebase Authenticaton component of the Firebase JS SDK.", - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/node/index.js", - "react-native": "dist/rn/index.js", - "browser": "dist/esm2017/index.js", - "cordova": "dist/cordova/index.esm5.js", - "module": "dist/esm2017/index.js", - "webworker": "dist/index.webworker.esm5.js", - "files": [ - "dist", - "cordova/package.json", - "internal/package.json", - "react-native/package.json" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:deps": "lerna run --scope @firebase/auth-exp --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", - "build:scripts": "tsc -moduleResolution node --module commonjs scripts/*.ts && ls scripts/*.js | xargs -I % sh -c 'terser % -o %'", - "dev": "rollup -c -w", - "test": "run-p lint test:all", - "test:all": "run-p test:browser:unit test:node:unit test:integration", - "test:integration": "firebase emulators:exec --project emulatedproject --only auth \"run-s test:browser:integration:local test:node:integration:local test:webdriver\"", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", - "test:integration:local": "run-s test:node:integration:local test:browser:integration:local test:webdriver", - "test:browser": "karma start --single-run --local", - "test:browser:unit": "karma start --single-run --unit", - "test:browser:integration": "karma start --single-run --integration", - "test:browser:integration:local": "karma start --single-run --integration --local", - "test:browser:debug": "karma start --auto-watch", - "test:browser:unit:debug": "karma start --auto-watch --unit", - "test:cordova": "karma start --single-run --cordova", - "test:cordova:debug": "karma start --auto-watch --cordova", - "test:node": "run-s test:node:unit test:node:integration:local", - "test:node:unit": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts", - "test:node:integration": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts --integration", - "test:node:integration:local": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts --integration --local", - "test:webdriver": "rollup -c test/integration/webdriver/static/rollup.config.js && ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts --webdriver", - "api-report": "api-extractor run --local --verbose && ts-node-script ../../repo-scripts/prune-dts/prune-dts.ts --input dist/auth-exp-public.d.ts --output dist/auth-exp-public.d.ts", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc", - "typings:public": "node ../../scripts/exp/use_typings.js ./dist/auth-exp-public.d.ts" - }, - "peerDependencies": { - "@firebase/app-exp": "0.x" - }, - "dependencies": { - "@firebase/component": "0.5.6", - "@firebase/logger": "0.2.6", - "@firebase/util": "1.3.0", - "node-fetch": "2.6.1", - "selenium-webdriver": "4.0.0-beta.1", - "tslib": "^2.1.0" - }, - "license": "Apache-2.0", - "devDependencies": { - "@firebase/app-exp": "0.0.900", - "@rollup/plugin-json": "4.1.0", - "rollup": "2.52.2", - "rollup-plugin-sourcemaps": "0.6.3", - "rollup-plugin-typescript2": "0.30.0", - "@rollup/plugin-strip": "2.0.1", - "typescript": "4.2.2" - }, - "repository": { - "directory": "packages-exp/auth-exp", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "typings": "dist/auth-exp.d.ts", - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - }, - "esm5": "dist/esm5/index.js" -} diff --git a/packages-exp/auth-exp/rollup.config.release.js b/packages-exp/auth-exp/rollup.config.release.js deleted file mode 100644 index 23c611a5b21..00000000000 --- a/packages-exp/auth-exp/rollup.config.release.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getConfig } from './rollup.config.shared'; - -export default getConfig({ isReleaseBuild: true }); diff --git a/packages-exp/auth-exp/rollup.config.shared.js b/packages-exp/auth-exp/rollup.config.shared.js deleted file mode 100644 index c494da323a5..00000000000 --- a/packages-exp/auth-exp/rollup.config.shared.js +++ /dev/null @@ -1,203 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import strip from '@rollup/plugin-strip'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import json from '@rollup/plugin-json'; -import alias from '@rollup/plugin-alias'; -import typescript from 'typescript'; -import pkg from './package.json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -/** - * Common plugins for all builds - */ -const commonPlugins = [ - json(), - strip({ - functions: ['debugAssert.*'] - }) -]; - -/** - * Node has the same entry point as browser, but browser-specific exports - * are turned into either no-ops or errors. See src/platform_node/index.ts for - * more info. This regex tests explicitly ./src/platform_browser so that the - * only impacted file is the main index.ts - */ -const nodeAliasPlugin = alias({ - entries: [ - { - find: /^\.\/src\/platform_browser(\/.*)?$/, - replacement: `./src/platform_node` - } - ] -}); - -export function getConfig({ isReleaseBuild }) { - /** - * ES5 Builds - */ - const es5BuildPlugins = [ - ...commonPlugins, - getTypesScriptPlugin({ isReleaseBuild }) - ]; - - const es5Builds = [ - /** - * Browser Builds - */ - { - input: { - index: 'index.ts', - internal: 'internal/index.ts' - }, - output: [{ dir: 'dist/esm5', format: 'esm', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - /** - * Web Worker Build (compiled without DOM) - */ - { - input: 'index.webworker.ts', - output: [{ file: pkg.webworker, format: 'es', sourcemap: true }], - plugins: [ - ...commonPlugins, - getTypesScriptPlugin({ - isReleaseBuild, - compilerOptions: { - lib: [ - // Remove dom after we figure out why navigator stuff doesn't exist - 'dom', - 'es2015', - 'webworker' - ] - } - }) - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - /** - * Node.js Build - */ - { - input: { - index: 'index.node.ts', - internal: 'internal/index.ts' - }, - output: [{ dir: 'dist/node', format: 'cjs', sourcemap: true }], - plugins: [nodeAliasPlugin, ...es5BuildPlugins], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - /** - * Cordova Builds - */ - { - input: { - index: 'index.cordova.ts', - internal: 'internal/index.ts' - }, - output: [{ dir: 'dist/cordova', format: 'es', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => - [...deps, 'cordova'].some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - /** - * React Native Builds - */ - { - input: { - index: 'index.rn.ts', - internal: 'internal/index.ts' - }, - output: [{ dir: 'dist/rn', format: 'cjs', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => - [...deps, 'react-native'].some( - dep => id === dep || id.startsWith(`${dep}/`) - ) - } - ]; - - /** - * ES2017 Builds - */ - const es2017BuildPlugins = [ - ...commonPlugins, - getTypesScriptPlugin({ - isReleaseBuild, - compilerOptions: { target: 'es2017' } - }) - ]; - - const es2017Builds = [ - /** - * Browser Builds - */ - { - input: { - index: 'index.ts', - internal: 'internal/index.ts' - }, - output: { - dir: 'dist/esm2017', - format: 'es', - sourcemap: true - }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } - ]; - - const allBuilds = [...es5Builds, ...es2017Builds]; - - if (isReleaseBuild) { - for (const build of allBuilds) { - build.treeshake = { - moduleSideEffects: false - }; - } - } - - return allBuilds; -} - -function getTypesScriptPlugin({ compilerOptions, isReleaseBuild }) { - let options = { - typescript, - tsconfigOverride: { - compilerOptions - } - }; - - if (isReleaseBuild) { - options = { - ...options, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }; - } - - return typescriptPlugin(options); -} diff --git a/packages-exp/firebase-exp/.gitignore b/packages-exp/firebase-exp/.gitignore deleted file mode 100644 index 228443ec909..00000000000 --- a/packages-exp/firebase-exp/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/firebase*.js -/firebase*.map -/firebase*.gz -/firebase*.tgz \ No newline at end of file diff --git a/packages-exp/firebase-exp/README.md b/packages-exp/firebase-exp/README.md deleted file mode 100644 index 0569dc7ea39..00000000000 --- a/packages-exp/firebase-exp/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Firebase - App success made simple - -## Overview - -TODO \ No newline at end of file diff --git a/packages-exp/firebase-exp/analytics/index.ts b/packages-exp/firebase-exp/analytics/index.ts deleted file mode 100644 index 586d64db7af..00000000000 --- a/packages-exp/firebase-exp/analytics/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from '@firebase/analytics-exp'; diff --git a/packages-exp/firebase-exp/analytics/package.json b/packages-exp/firebase-exp/analytics/package.json deleted file mode 100644 index a4efc64b610..00000000000 --- a/packages-exp/firebase-exp/analytics/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/analytics", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/analytics/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/app-check/package.json b/packages-exp/firebase-exp/app-check/package.json deleted file mode 100644 index 2ae4cd95979..00000000000 --- a/packages-exp/firebase-exp/app-check/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/app-check", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/app-check/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/app/index.ts b/packages-exp/firebase-exp/app/index.ts deleted file mode 100644 index 0ecb5e28753..00000000000 --- a/packages-exp/firebase-exp/app/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { registerVersion } from '@firebase/app-exp'; -import { name, version } from '../package.json'; - -registerVersion(name, version, 'app'); -export * from '@firebase/app-exp'; diff --git a/packages-exp/firebase-exp/app/package.json b/packages-exp/firebase-exp/app/package.json deleted file mode 100644 index 7f3262bd49a..00000000000 --- a/packages-exp/firebase-exp/app/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/app", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/app/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/auth/cordova/package.json b/packages-exp/firebase-exp/auth/cordova/package.json deleted file mode 100644 index cb4dc1f626c..00000000000 --- a/packages-exp/firebase-exp/auth/cordova/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/auth/cordova", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/auth/cordova/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/auth/index.ts b/packages-exp/firebase-exp/auth/index.ts deleted file mode 100644 index 2505d8e8794..00000000000 --- a/packages-exp/firebase-exp/auth/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from '@firebase/auth-exp'; diff --git a/packages-exp/firebase-exp/auth/package.json b/packages-exp/firebase-exp/auth/package.json deleted file mode 100644 index 8508ef7aa5a..00000000000 --- a/packages-exp/firebase-exp/auth/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/auth", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/auth/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/auth/react-native/package.json b/packages-exp/firebase-exp/auth/react-native/package.json deleted file mode 100644 index fc5de5175c1..00000000000 --- a/packages-exp/firebase-exp/auth/react-native/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/auth/react-native", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/auth/react-native/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/analytics/package.json b/packages-exp/firebase-exp/compat/analytics/package.json deleted file mode 100644 index aac4bf9d943..00000000000 --- a/packages-exp/firebase-exp/compat/analytics/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/analytics", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/analytics/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/app-check/package.json b/packages-exp/firebase-exp/compat/app-check/package.json deleted file mode 100644 index 0415ce945d0..00000000000 --- a/packages-exp/firebase-exp/compat/app-check/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/app-check", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/app-check/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/app/package.json b/packages-exp/firebase-exp/compat/app/package.json deleted file mode 100644 index 5378fc49169..00000000000 --- a/packages-exp/firebase-exp/compat/app/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/app", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "../index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/auth/package.json b/packages-exp/firebase-exp/compat/auth/package.json deleted file mode 100644 index dd0dbcbe355..00000000000 --- a/packages-exp/firebase-exp/compat/auth/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/auth", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/auth/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/database/package.json b/packages-exp/firebase-exp/compat/database/package.json deleted file mode 100644 index e685c37b115..00000000000 --- a/packages-exp/firebase-exp/compat/database/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/database", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/database/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/firestore/package.json b/packages-exp/firebase-exp/compat/firestore/package.json deleted file mode 100644 index a479c4d16e9..00000000000 --- a/packages-exp/firebase-exp/compat/firestore/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/firestore", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/firestore/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/functions/package.json b/packages-exp/firebase-exp/compat/functions/package.json deleted file mode 100644 index 1e88cb48e33..00000000000 --- a/packages-exp/firebase-exp/compat/functions/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/functions", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/functions/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/index.d.ts b/packages-exp/firebase-exp/compat/index.d.ts deleted file mode 100644 index 43a9b4e3f28..00000000000 --- a/packages-exp/firebase-exp/compat/index.d.ts +++ /dev/null @@ -1,10091 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * firebase is a global namespace from which all Firebase - * services are accessed. - */ -declare namespace firebase { - /** - * @hidden - */ - type NextFn = (value: T) => void; - /** - * @hidden - */ - type ErrorFn = (error: E) => void; - /** - * @hidden - */ - type CompleteFn = () => void; - - /** - * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In - * addition to a message string and stack trace, it contains a string code. - */ - interface FirebaseError { - /** - * Error codes are strings using the following format: `"service/string-code"`. - * Some examples include `"app/no-app"` and `"auth/user-not-found"`. - * - * While the message for a given error can change, the code will remain the same - * between backward-compatible versions of the Firebase SDK. - */ - code: string; - /** - * An explanatory message for the error that just occurred. - * - * This message is designed to be helpful to you, the developer. Because - * it generally does not convey meaningful information to end users, - * this message should not be displayed in your application. - */ - message: string; - /** - * The name of the class of errors, which is `"FirebaseError"`. - */ - name: 'FirebaseError'; - /** - * A string value containing the execution backtrace when the error originally - * occurred. This may not always be available. - * - * When it is available, this information can be sent to - * {@link https://firebase.google.com/support/ Firebase Support} to help - * explain the cause of an error. - */ - stack?: string; - } - - /** - * @hidden - */ - interface Observer { - next: NextFn; - error: ErrorFn; - complete: CompleteFn; - } - - /** - * The JS SDK supports 5 log levels and also allows a user the ability to - * silence the logs altogether. - * - * The order is as follows: - * silent < debug < verbose < info < warn < error - */ - type LogLevel = 'debug' | 'verbose' | 'info' | 'warn' | 'error' | 'silent'; - - /** - * The current SDK version. - */ - var SDK_VERSION: string; - - /** - * Registers a library's name and version for platform logging purposes. - * @param library Name of 1p or 3p library (e.g. firestore, angularfire) - * @param version Current version of that library. - * @param variant Bundle variant, e.g., node, rn, etc. - */ - function registerVersion( - library: string, - version: string, - variant?: string - ): void; - - /** - * Sets log level for all Firebase packages. - * - * All of the log types above the current log level are captured (i.e. if - * you set the log level to `info`, errors are logged, but `debug` and - * `verbose` logs are not). - */ - function setLogLevel(logLevel: LogLevel): void; - - /** - * Sets log handler for all Firebase packages. - * @param logCallback An optional custom log handler that executes user code whenever - * the Firebase SDK makes a logging call. - */ - function onLog( - logCallback: (callbackParams: { - /** - * Level of event logged. - */ - level: LogLevel; - /** - * Any text from logged arguments joined into one string. - */ - message: string; - /** - * The raw arguments passed to the log call. - */ - args: any[]; - /** - * A string indicating the name of the package that made the log call, - * such as `@firebase/firestore`. - */ - type: string; - }) => void, - options?: { - /** - * Threshhold log level. Only logs at or above this level trigger the `logCallback` - * passed to `onLog`. - */ - level: LogLevel; - } - ): void; - - /** - * @hidden - */ - type Unsubscribe = () => void; - - /** - * A user account. - */ - interface User extends firebase.UserInfo { - /** - * Deletes and signs out the user. - * - * Important: this is a security-sensitive operation that requires the - * user to have recently signed in. If this requirement isn't met, ask the user - * to authenticate again and then call - * {@link firebase.User.reauthenticateWithCredential}. - * - *

Error Codes

- *
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User.reauthenticateWithCredential} to - * resolve. This does not apply if the user is anonymous.
- *
- */ - delete(): Promise; - emailVerified: boolean; - getIdTokenResult( - forceRefresh?: boolean - ): Promise; - /** - * Returns a JSON Web Token (JWT) used to identify the user to a Firebase - * service. - * - * Returns the current token if it has not expired. Otherwise, this will - * refresh the token and return a new one. - * - * @param forceRefresh Force refresh regardless of token - * expiration. - */ - getIdToken(forceRefresh?: boolean): Promise; - isAnonymous: boolean; - /** - * Links the user account with the given credentials and returns any available - * additional user information, such as user name. - * - *

Error Codes

- *
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/invalid-credential
- *
Thrown if the provider's credential is not valid. This can happen if it - * has already expired when calling link, or if it used invalid token(s). - * See the Firebase documentation for your provider, and make sure you pass - * in the correct parameters to the credential method.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * The fields error.email, error.phoneNumber, and - * error.credential ({@link firebase.auth.AuthCredential}) - * may be provided, depending on the type of credential. You can recover - * from this error by signing in with error.credential directly - * via {@link firebase.auth.Auth.signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User.linkWithCredential} the original credential to that - * newly signed in user.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/invalid-email
- *
Thrown if the email used in a - * {@link firebase.auth.EmailAuthProvider.credential} is invalid.
- *
auth/wrong-password
- *
Thrown if the password used in a - * {@link firebase.auth.EmailAuthProvider.credential} is not correct or - * when the user associated with the email does not have a password.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
- * - * @deprecated This method is deprecated. Use - * {@link firebase.User.linkWithCredential} instead. - * - * @param credential The auth credential. - */ - linkAndRetrieveDataWithCredential( - credential: firebase.auth.AuthCredential - ): Promise; - /** - * Links the user account with the given credentials. - * - *

Error Codes

- *
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/invalid-credential
- *
Thrown if the provider's credential is not valid. This can happen if it - * has already expired when calling link, or if it used invalid token(s). - * See the Firebase documentation for your provider, and make sure you pass - * in the correct parameters to the credential method.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * The fields error.email, error.phoneNumber, and - * error.credential ({@link firebase.auth.AuthCredential}) - * may be provided, depending on the type of credential. You can recover - * from this error by signing in with error.credential directly - * via {@link firebase.auth.Auth.signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User.linkWithCredential} the original credential to that - * newly signed in user.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/invalid-email
- *
Thrown if the email used in a - * {@link firebase.auth.EmailAuthProvider.credential} is invalid.
- *
auth/wrong-password
- *
Thrown if the password used in a - * {@link firebase.auth.EmailAuthProvider.credential} is not correct or - * when the user associated with the email does not have a password.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
- * - * @param credential The auth credential. - */ - linkWithCredential( - credential: firebase.auth.AuthCredential - ): Promise; - /** - * Links the user account with the given phone number. - * - *

Error Codes

- *
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/captcha-check-failed
- *
Thrown if the reCAPTCHA response token was invalid, expired, or if - * this method was called from a non-whitelisted domain.
- *
auth/invalid-phone-number
- *
Thrown if the phone number has an invalid format.
- *
auth/missing-phone-number
- *
Thrown if the phone number is missing.
- *
auth/quota-exceeded
- *
Thrown if the SMS quota for the Firebase project has been exceeded.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given phone number has been - * disabled.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the phone number already exists - * among your users, or is already linked to a Firebase User. - * The fields error.phoneNumber and - * error.credential ({@link firebase.auth.AuthCredential}) - * are provided in this case. You can recover from this error by signing in - * with that credential directly via - * {@link firebase.auth.Auth.signInWithCredential}.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the phone authentication provider in the - * Firebase Console. Go to the Firebase Console for your project, in the - * Auth section and the Sign in Method tab and configure - * the provider.
- *
- * - * @param phoneNumber The user's phone number in E.164 format (e.g. - * +16505550101). - * @param applicationVerifier - */ - linkWithPhoneNumber( - phoneNumber: string, - applicationVerifier: firebase.auth.ApplicationVerifier - ): Promise; - /** - * Links the authenticated provider to the user account using a pop-up based - * OAuth flow. - * - * If the linking is successful, the returned result will contain the user - * and the provider's credential. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/cancelled-popup-request
- *
Thrown if successive popup operations are triggered. Only one popup - * request is allowed at one time on a user or an auth instance. All the - * popups would fail with this error except for the last one.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * An error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. You can - * recover from this error by signing in with that credential directly via - * {@link firebase.auth.Auth.signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User.linkWithCredential} the original credential to that - * newly signed in user.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/popup-blocked
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
Thrown if the popup was blocked by the browser, typically when this - * operation is triggered outside of a click handler.
- *
auth/popup-closed-by-user
- *
Thrown if the popup window is closed by the user without completing the - * sign in to the provider.
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @webonly - * - * @example - * ```javascript - * // Creates the provider object. - * var provider = new firebase.auth.FacebookAuthProvider(); - * // You can add additional scopes to the provider: - * provider.addScope('email'); - * provider.addScope('user_friends'); - * // Link with popup: - * user.linkWithPopup(provider).then(function(result) { - * // The firebase.User instance: - * var user = result.user; - * // The Facebook firebase.auth.AuthCredential containing the Facebook - * // access token: - * var credential = result.credential; - * }, function(error) { - * // An error happened. - * }); - * ``` - * - * @param provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - */ - linkWithPopup( - provider: firebase.auth.AuthProvider - ): Promise; - /** - * Links the authenticated provider to the user account using a full-page - * redirect flow. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @param provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - */ - linkWithRedirect(provider: firebase.auth.AuthProvider): Promise; - metadata: firebase.auth.UserMetadata; - /** - * The {@link firebase.User.MultiFactorUser} object corresponding to the current user. - * This is used to access all multi-factor properties and operations related to the - * current user. - */ - - multiFactor: firebase.User.MultiFactorUser; - /** - * The phone number normalized based on the E.164 standard (e.g. +16505550101) - * for the current user. This is null if the user has no phone credential linked - * to the account. - */ - phoneNumber: string | null; - providerData: (firebase.UserInfo | null)[]; - /** - * Re-authenticates a user using a fresh credential, and returns any available - * additional user information, such as user name. Use before operations - * such as {@link firebase.User.updatePassword} that require tokens from recent - * sign-in attempts. - * - *

Error Codes

- *
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/user-not-found
- *
Thrown if the credential given does not correspond to any existing user. - *
- *
auth/invalid-credential
- *
Thrown if the provider's credential is not valid. This can happen if it - * has already expired when calling link, or if it used invalid token(s). - * See the Firebase documentation for your provider, and make sure you pass - * in the correct parameters to the credential method.
- *
auth/invalid-email
- *
Thrown if the email used in a - * {@link firebase.auth.EmailAuthProvider.credential} is invalid.
- *
auth/wrong-password
- *
Thrown if the password used in a - * {@link firebase.auth.EmailAuthProvider.credential} is not correct or when - * the user associated with the email does not have a password.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
- * - * @deprecated - * This method is deprecated. Use - * {@link firebase.User.reauthenticateWithCredential} instead. - * - * @param credential - */ - reauthenticateAndRetrieveDataWithCredential( - credential: firebase.auth.AuthCredential - ): Promise; - /** - * Re-authenticates a user using a fresh credential. Use before operations - * such as {@link firebase.User.updatePassword} that require tokens from recent - * sign-in attempts. - * - *

Error Codes

- *
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/user-not-found
- *
Thrown if the credential given does not correspond to any existing user. - *
- *
auth/invalid-credential
- *
Thrown if the provider's credential is not valid. This can happen if it - * has already expired when calling link, or if it used invalid token(s). - * See the Firebase documentation for your provider, and make sure you pass - * in the correct parameters to the credential method.
- *
auth/invalid-email
- *
Thrown if the email used in a - * {@link firebase.auth.EmailAuthProvider.credential} is invalid.
- *
auth/wrong-password
- *
Thrown if the password used in a - * {@link firebase.auth.EmailAuthProvider.credential} is not correct or when - * the user associated with the email does not have a password.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
- * - * @param credential - */ - reauthenticateWithCredential( - credential: firebase.auth.AuthCredential - ): Promise; - /** - * Re-authenticates a user using a fresh credential. Use before operations - * such as {@link firebase.User.updatePassword} that require tokens from recent - * sign-in attempts. - * - *

Error Codes

- *
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/user-not-found
- *
Thrown if the credential given does not correspond to any existing user. - *
- *
auth/captcha-check-failed
- *
Thrown if the reCAPTCHA response token was invalid, expired, or if - * this method was called from a non-whitelisted domain.
- *
auth/invalid-phone-number
- *
Thrown if the phone number has an invalid format.
- *
auth/missing-phone-number
- *
Thrown if the phone number is missing.
- *
auth/quota-exceeded
- *
Thrown if the SMS quota for the Firebase project has been exceeded.
- *
- * - * @param phoneNumber The user's phone number in E.164 format (e.g. - * +16505550101). - * @param applicationVerifier - */ - reauthenticateWithPhoneNumber( - phoneNumber: string, - applicationVerifier: firebase.auth.ApplicationVerifier - ): Promise; - /** - * Reauthenticates the current user with the specified provider using a pop-up - * based OAuth flow. - * - * If the reauthentication is successful, the returned result will contain the - * user and the provider's credential. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/cancelled-popup-request
- *
Thrown if successive popup operations are triggered. Only one popup - * request is allowed at one time on a user or an auth instance. All the - * popups would fail with this error except for the last one.
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/popup-blocked
- *
Thrown if the popup was blocked by the browser, typically when this - * operation is triggered outside of a click handler.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/popup-closed-by-user
- *
Thrown if the popup window is closed by the user without completing the - * sign in to the provider.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @webonly - * - * @example - * ```javascript - * // Creates the provider object. - * var provider = new firebase.auth.FacebookAuthProvider(); - * // You can add additional scopes to the provider: - * provider.addScope('email'); - * provider.addScope('user_friends'); - * // Reauthenticate with popup: - * user.reauthenticateWithPopup(provider).then(function(result) { - * // The firebase.User instance: - * var user = result.user; - * // The Facebook firebase.auth.AuthCredential containing the Facebook - * // access token: - * var credential = result.credential; - * }, function(error) { - * // An error happened. - * }); - * ``` - * - * @param provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - */ - reauthenticateWithPopup( - provider: firebase.auth.AuthProvider - ): Promise; - /** - * Reauthenticates the current user with the specified OAuth provider using a - * full-page redirect flow. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @webonly - * - * @param provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - */ - reauthenticateWithRedirect( - provider: firebase.auth.AuthProvider - ): Promise; - refreshToken: string; - /** - * Refreshes the current user, if signed in. - * - */ - reload(): Promise; - /** - * Sends a verification email to a user. - * - * The verification process is completed by calling - * {@link firebase.auth.Auth.applyActionCode} - * - *

Error Codes

- *
- *
auth/missing-android-pkg-name
- *
An Android package name must be provided if the Android app is required - * to be installed.
- *
auth/missing-continue-uri
- *
A continue URL must be provided in the request.
- *
auth/missing-ios-bundle-id
- *
An iOS bundle ID must be provided if an App Store ID is provided.
- *
auth/invalid-continue-uri
- *
The continue URL provided in the request is invalid.
- *
auth/unauthorized-continue-uri
- *
The domain of the continue URL is not whitelisted. Whitelist - * the domain in the Firebase console.
- *
- * - * @example - * ```javascript - * var actionCodeSettings = { - * url: 'https://www.example.com/cart?email=user@example.com&cartId=123', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * handleCodeInApp: true - * }; - * firebase.auth().currentUser.sendEmailVerification(actionCodeSettings) - * .then(function() { - * // Verification email sent. - * }) - * .catch(function(error) { - * // Error occurred. Inspect error.code. - * }); - * ``` - * - * @param actionCodeSettings The action - * code settings. If specified, the state/continue URL will be set as the - * "continueUrl" parameter in the email verification link. The default email - * verification landing page will use this to display a link to go back to - * the app if it is installed. - * If the actionCodeSettings is not specified, no URL is appended to the - * action URL. - * The state URL provided must belong to a domain that is whitelisted by the - * developer in the console. Otherwise an error will be thrown. - * Mobile app redirects will only be applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of condition. - * The Android package name and iOS bundle ID will be respected only if they - * are configured in the same Firebase Auth project used. - */ - sendEmailVerification( - actionCodeSettings?: firebase.auth.ActionCodeSettings | null - ): Promise; - /** - * The current user's tenant ID. This is a read-only property, which indicates - * the tenant ID used to sign in the current user. This is null if the user is - * signed in from the parent project. - * - * @example - * ```javascript - * // Set the tenant ID on Auth instance. - * firebase.auth().tenantId = ‘TENANT_PROJECT_ID’; - * - * // All future sign-in request now include tenant ID. - * firebase.auth().signInWithEmailAndPassword(email, password) - * .then(function(result) { - * // result.user.tenantId should be ‘TENANT_PROJECT_ID’. - * }).catch(function(error) { - * // Handle error. - * }); - * ``` - */ - tenantId: string | null; - /** - * Returns a JSON-serializable representation of this object. - * - * @return A JSON-serializable representation of this object. - */ - toJSON(): Object; - /** - * Unlinks a provider from a user account. - * - *

Error Codes

- *
- *
auth/no-such-provider
- *
Thrown if the user does not have this provider linked or when the - * provider ID given does not exist.
- * - * - * @param providerId - */ - unlink(providerId: string): Promise; - /** - * Updates the user's email address. - * - * An email will be sent to the original email address (if it was set) that - * allows to revoke the email address change, in order to protect them from - * account hijacking. - * - * Important: this is a security sensitive operation that requires the - * user to have recently signed in. If this requirement isn't met, ask the user - * to authenticate again and then call - * {@link firebase.User.reauthenticateWithCredential}. - * - *

Error Codes

- *
- *
auth/invalid-email
- *
Thrown if the email used is invalid.
- *
auth/email-already-in-use
- *
Thrown if the email is already used by another user.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User.reauthenticateWithCredential} to - * resolve. This does not apply if the user is anonymous.
- *
- * - * @param newEmail The new email address. - */ - updateEmail(newEmail: string): Promise; - /** - * Updates the user's password. - * - * Important: this is a security sensitive operation that requires the - * user to have recently signed in. If this requirement isn't met, ask the user - * to authenticate again and then call - * {@link firebase.User.reauthenticateWithCredential}. - * - *

Error Codes

- *
- *
auth/weak-password
- *
Thrown if the password is not strong enough.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User.reauthenticateWithCredential} to - * resolve. This does not apply if the user is anonymous.
- *
- * - * @param newPassword - */ - updatePassword(newPassword: string): Promise; - /** - * Updates the user's phone number. - * - *

Error Codes

- *
- *
auth/invalid-verification-code
- *
Thrown if the verification code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the verification ID of the credential is not valid.
- *
- * - * @param phoneCredential - */ - updatePhoneNumber( - phoneCredential: firebase.auth.AuthCredential - ): Promise; - /** - * Updates a user's profile data. - * - * @example - * ```javascript - * // Updates the user attributes: - * user.updateProfile({ - * displayName: "Jane Q. User", - * photoURL: "https://example.com/jane-q-user/profile.jpg" - * }).then(function() { - * // Profile updated successfully! - * // "Jane Q. User" - * var displayName = user.displayName; - * // "https://example.com/jane-q-user/profile.jpg" - * var photoURL = user.photoURL; - * }, function(error) { - * // An error happened. - * }); - * - * // Passing a null value will delete the current attribute's value, but not - * // passing a property won't change the current attribute's value: - * // Let's say we're using the same user than before, after the update. - * user.updateProfile({photoURL: null}).then(function() { - * // Profile updated successfully! - * // "Jane Q. User", hasn't changed. - * var displayName = user.displayName; - * // Now, this is null. - * var photoURL = user.photoURL; - * }, function(error) { - * // An error happened. - * }); - * ``` - * - * @param profile The profile's - * displayName and photoURL to update. - */ - updateProfile(profile: { - displayName?: string | null; - photoURL?: string | null; - }): Promise; - /** - * Sends a verification email to a new email address. The user's email will be - * updated to the new one after being verified. - * - * If you have a custom email action handler, you can complete the verification - * process by calling {@link firebase.auth.Auth.applyActionCode}. - * - *

Error Codes

- *
- *
auth/missing-android-pkg-name
- *
An Android package name must be provided if the Android app is required - * to be installed.
- *
auth/missing-continue-uri
- *
A continue URL must be provided in the request.
- *
auth/missing-ios-bundle-id
- *
An iOS bundle ID must be provided if an App Store ID is provided.
- *
auth/invalid-continue-uri
- *
The continue URL provided in the request is invalid.
- *
auth/unauthorized-continue-uri
- *
The domain of the continue URL is not whitelisted. Whitelist - * the domain in the Firebase console.
- *
- * - * @example - * ```javascript - * var actionCodeSettings = { - * url: 'https://www.example.com/cart?email=user@example.com&cartId=123', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * handleCodeInApp: true - * }; - * firebase.auth().currentUser.verifyBeforeUpdateEmail( - * 'user@example.com', actionCodeSettings) - * .then(function() { - * // Verification email sent. - * }) - * .catch(function(error) { - * // Error occurred. Inspect error.code. - * }); - * ``` - * - * @param newEmail The email address to be verified and updated to. - * @param actionCodeSettings The action - * code settings. If specified, the state/continue URL will be set as the - * "continueUrl" parameter in the email verification link. The default email - * verification landing page will use this to display a link to go back to - * the app if it is installed. - * If the actionCodeSettings is not specified, no URL is appended to the - * action URL. - * The state URL provided must belong to a domain that is whitelisted by the - * developer in the console. Otherwise an error will be thrown. - * Mobile app redirects will only be applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of condition. - * The Android package name and iOS bundle ID will be respected only if they - * are configured in the same Firebase Auth project used. - */ - verifyBeforeUpdateEmail( - newEmail: string, - actionCodeSettings?: firebase.auth.ActionCodeSettings | null - ): Promise; - } - - /** - * User profile information, visible only to the Firebase project's - * apps. - * - */ - interface UserInfo { - displayName: string | null; - email: string | null; - phoneNumber: string | null; - photoURL: string | null; - providerId: string; - /** - * The user's unique ID. - */ - uid: string; - } - - /** - * Retrieves a Firebase {@link firebase.app.App app} instance. - * - * When called with no arguments, the default app is returned. When an app name - * is provided, the app corresponding to that name is returned. - * - * An exception is thrown if the app being retrieved has not yet been - * initialized. - * - * @example - * ```javascript - * // Return the default app - * var app = firebase.app(); - * ``` - * - * @example - * ```javascript - * // Return a named app - * var otherApp = firebase.app("otherApp"); - * ``` - * - * @param name Optional name of the app to return. If no name is - * provided, the default is `"[DEFAULT]"`. - * - * @return The app corresponding to the provided app name. - * If no app name is provided, the default app is returned. - */ - function app(name?: string): firebase.app.App; - - /** - * A (read-only) array of all initialized apps. - */ - var apps: firebase.app.App[]; - - /** - * Gets the {@link firebase.auth.Auth `Auth`} service for the default app or a - * given app. - * - * `firebase.auth()` can be called with no arguments to access the default app's - * {@link firebase.auth.Auth `Auth`} service or as `firebase.auth(app)` to - * access the {@link firebase.auth.Auth `Auth`} service associated with a - * specific app. - * - * @example - * ```javascript - * - * // Get the Auth service for the default app - * var defaultAuth = firebase.auth(); - * ``` - * @example - * ```javascript - * - * // Get the Auth service for a given app - * var otherAuth = firebase.auth(otherApp); - * ``` - * @param app - */ - function auth(app?: firebase.app.App): firebase.auth.Auth; - - /** - * Gets the {@link firebase.database.Database `Database`} service for the - * default app or a given app. - * - * `firebase.database()` can be called with no arguments to access the default - * app's {@link firebase.database.Database `Database`} service or as - * `firebase.database(app)` to access the - * {@link firebase.database.Database `Database`} service associated with a - * specific app. - * - * `firebase.database` is also a namespace that can be used to access global - * constants and methods associated with the `Database` service. - * - * @example - * ```javascript - * // Get the Database service for the default app - * var defaultDatabase = firebase.database(); - * ``` - * - * @example - * ```javascript - * // Get the Database service for a specific app - * var otherDatabase = firebase.database(app); - * ``` - * - * @namespace - * @param app Optional app whose Database service to - * return. If not provided, the default Database service will be returned. - * @return The default Database service if no app - * is provided or the Database service associated with the provided app. - */ - function database(app?: firebase.app.App): firebase.database.Database; - - /** - * Creates and initializes a Firebase {@link firebase.app.App app} instance. - * - * See - * {@link - * https://firebase.google.com/docs/web/setup#add_firebase_to_your_app - * Add Firebase to your app} and - * {@link - * https://firebase.google.com/docs/web/learn-more#multiple-projects - * Initialize multiple projects} for detailed documentation. - * - * @example - * ```javascript - * - * // Initialize default app - * // Retrieve your own options values by adding a web app on - * // https://console.firebase.google.com - * firebase.initializeApp({ - * apiKey: "AIza....", // Auth / General Use - * appId: "1:27992087142:web:ce....", // General Use - * projectId: "my-firebase-project", // General Use - * authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect - * databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database - * storageBucket: "YOUR_APP.appspot.com", // Storage - * messagingSenderId: "123456789", // Cloud Messaging - * measurementId: "G-12345" // Analytics - * }); - * ``` - * - * @example - * ```javascript - * - * // Initialize another app - * var otherApp = firebase.initializeApp({ - * apiKey: "AIza....", - * appId: "1:27992087142:web:ce....", - * projectId: "my-firebase-project", - * databaseURL: "https://.firebaseio.com", - * storageBucket: ".appspot.com" - * }, "nameOfOtherApp"); - * ``` - * - * @param options Options to configure the app's services. - * @param name Optional name of the app to initialize. If no name - * is provided, the default is `"[DEFAULT]"`. - * - * @return {!firebase.app.App} The initialized app. - */ - function initializeApp(options: Object, name?: string): firebase.app.App; - - /** - * Gets the {@link firebase.messaging.Messaging `Messaging`} service for the - * default app or a given app. - * - * `firebase.messaging()` can be called with no arguments to access the default - * app's {@link firebase.messaging.Messaging `Messaging`} service or as - * `firebase.messaging(app)` to access the - * {@link firebase.messaging.Messaging `Messaging`} service associated with a - * specific app. - * - * Calling `firebase.messaging()` in a service worker results in Firebase - * generating notifications if the push message payload has a `notification` - * parameter. - * - * @webonly - * - * @example - * ```javascript - * // Get the Messaging service for the default app - * var defaultMessaging = firebase.messaging(); - * ``` - * - * @example - * ```javascript - * // Get the Messaging service for a given app - * var otherMessaging = firebase.messaging(otherApp); - * ``` - * - * @namespace - * @param app The app to create a Messaging service for. - * If not passed, uses the default app. - */ - function messaging(app?: firebase.app.App): firebase.messaging.Messaging; - - /** - * Gets the {@link firebase.storage.Storage `Storage`} service for the default - * app or a given app. - * - * `firebase.storage()` can be called with no arguments to access the default - * app's {@link firebase.storage.Storage `Storage`} service or as - * `firebase.storage(app)` to access the - * {@link firebase.storage.Storage `Storage`} service associated with a - * specific app. - * - * @webonly - * - * @example - * ```javascript - * // Get the Storage service for the default app - * var defaultStorage = firebase.storage(); - * ``` - * - * @example - * ```javascript - * // Get the Storage service for a given app - * var otherStorage = firebase.storage(otherApp); - * ``` - * - * @param app The app to create a storage service for. - * If not passed, uses the default app. - */ - function storage(app?: firebase.app.App): firebase.storage.Storage; - - function firestore(app?: firebase.app.App): firebase.firestore.Firestore; - - function functions(app?: firebase.app.App): firebase.functions.Functions; - - /** - * Gets the {@link firebase.performance.Performance `Performance`} service. - * - * `firebase.performance()` can be called with no arguments to access the default - * app's {@link firebase.performance.Performance `Performance`} service. - * The {@link firebase.performance.Performance `Performance`} service does not work with - * any other app. - * - * @webonly - * - * @example - * ```javascript - * // Get the Performance service for the default app - * const defaultPerformance = firebase.performance(); - * ``` - * - * @param app The app to create a performance service for. Performance Monitoring only works with - * the default app. - * If not passed, uses the default app. - */ - function performance( - app?: firebase.app.App - ): firebase.performance.Performance; - - /** - * Gets the {@link firebase.remoteConfig.RemoteConfig `RemoteConfig`} instance. - * - * @webonly - * - * @example - * ```javascript - * // Get the RemoteConfig instance for the default app - * const defaultRemoteConfig = firebase.remoteConfig(); - * ``` - * - * @param app The app to create a Remote Config service for. If not passed, uses the default app. - */ - function remoteConfig( - app?: firebase.app.App - ): firebase.remoteConfig.RemoteConfig; - - /** - * Gets the {@link firebase.analytics.Analytics `Analytics`} service. - * - * `firebase.analytics()` can be called with no arguments to access the default - * app's {@link firebase.analytics.Analytics `Analytics`} service. - * - * @webonly - * - * @example - * ```javascript - * // Get the Analytics service for the default app - * const defaultAnalytics = firebase.analytics(); - * ``` - * - * @param app The app to create an analytics service for. - * If not passed, uses the default app. - */ - function analytics(app?: firebase.app.App): firebase.analytics.Analytics; - - function appCheck(app?: firebase.app.App): firebase.appCheck.AppCheck; -} - -declare namespace firebase.app { - interface FirebaseOptions { - apiKey?: string; - authDomain?: string; - databaseURL?: string; - projectId?: string; - storageBucket?: string; - messagingSenderId?: string; - appId?: string; - measurementId?: string; - } - /** - * A Firebase App holds the initialization information for a collection of - * services. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.initializeApp|`firebase.initializeApp()`} to create an app. - * - */ - interface App { - /** - * Gets the {@link firebase.auth.Auth `Auth`} service for the current app. - * - * @example - * ```javascript - * var auth = app.auth(); - * // The above is shorthand for: - * // var auth = firebase.auth(app); - * ``` - */ - auth(): firebase.auth.Auth; - /** - * Gets the {@link firebase.database.Database `Database`} service for the - * current app. - * - * @example - * ```javascript - * var database = app.database(); - * // The above is shorthand for: - * // var database = firebase.database(app); - * ``` - */ - database(url?: string): firebase.database.Database; - /** - * Renders this app unusable and frees the resources of all associated - * services. - * - * @example - * ```javascript - * app.delete() - * .then(function() { - * console.log("App deleted successfully"); - * }) - * .catch(function(error) { - * console.log("Error deleting app:", error); - * }); - * ``` - */ - delete(): Promise; - /** - * Gets the {@link firebase.installations.Installations `Installations`} service for the - * current app. - * - * @webonly - * - * @example - * ```javascript - * const installations = app.installations(); - * // The above is shorthand for: - * // const installations = firebase.installations(app); - * ``` - */ - installations(): firebase.installations.Installations; - /** - * Gets the {@link firebase.messaging.Messaging `Messaging`} service for the - * current app. - * - * @webonly - * - * @example - * ```javascript - * var messaging = app.messaging(); - * // The above is shorthand for: - * // var messaging = firebase.messaging(app); - * ``` - */ - messaging(): firebase.messaging.Messaging; - /** - * The (read-only) name for this app. - * - * The default app's name is `"[DEFAULT]"`. - * - * @example - * ```javascript - * // The default app's name is "[DEFAULT]" - * firebase.initializeApp(defaultAppConfig); - * console.log(firebase.app().name); // "[DEFAULT]" - * ``` - * - * @example - * ```javascript - * // A named app's name is what you provide to initializeApp() - * var otherApp = firebase.initializeApp(otherAppConfig, "other"); - * console.log(otherApp.name); // "other" - * ``` - */ - name: string; - /** - * The (read-only) configuration options for this app. These are the original - * parameters given in - * {@link firebase.initializeApp `firebase.initializeApp()`}. - * - * @example - * ```javascript - * var app = firebase.initializeApp(config); - * console.log(app.options.databaseURL === config.databaseURL); // true - * ``` - */ - options: FirebaseOptions; - - /** - * The settable config flag for GDPR opt-in/opt-out - */ - automaticDataCollectionEnabled: boolean; - - /** - * Make the given App unusable and free resources. - */ - delete(): Promise; - - /** - * Gets the {@link firebase.storage.Storage `Storage`} service for the current - * app, optionally initialized with a custom storage bucket. - * - * @webonly - * - * @example - * ```javascript - * var storage = app.storage(); - * // The above is shorthand for: - * // var storage = firebase.storage(app); - * ``` - * - * @example - * ```javascript - * var storage = app.storage("gs://your-app.appspot.com"); - * ``` - * - * @param url The gs:// url to your Firebase Storage Bucket. - * If not passed, uses the app's default Storage Bucket. - */ - storage(url?: string): firebase.storage.Storage; - firestore(): firebase.firestore.Firestore; - functions(regionOrCustomDomain?: string): firebase.functions.Functions; - /** - * Gets the {@link firebase.performance.Performance `Performance`} service for the - * current app. If the current app is not the default one, throws an error. - * - * @webonly - * - * @example - * ```javascript - * const perf = app.performance(); - * // The above is shorthand for: - * // const perf = firebase.performance(app); - * ``` - */ - performance(): firebase.performance.Performance; - /** - * Gets the {@link firebase.remoteConfig.RemoteConfig `RemoteConfig`} instance. - * - * @webonly - * - * @example - * ```javascript - * const rc = app.remoteConfig(); - * // The above is shorthand for: - * // const rc = firebase.remoteConfig(app); - * ``` - */ - remoteConfig(): firebase.remoteConfig.RemoteConfig; - /** - * Gets the {@link firebase.analytics.Analytics `Analytics`} service for the - * current app. If the current app is not the default one, throws an error. - * - * @webonly - * - * @example - * ```javascript - * const analytics = app.analytics(); - * // The above is shorthand for: - * // const analytics = firebase.analytics(app); - * ``` - */ - analytics(): firebase.analytics.Analytics; - appCheck(): firebase.appCheck.AppCheck; - } -} - -declare namespace firebase.appCheck { - /** - * Result returned by - * {@link firebase.appCheck.AppCheck.getToken `firebase.appCheck().getToken()`}. - */ - interface AppCheckTokenResult { - token: string; - } - /** - * The Firebase AppCheck service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.appCheck `firebase.appCheck()`}. - */ - export interface AppCheck { - /** - * Activate AppCheck - * @param siteKeyOrProvider reCAPTCHA v3 site key (public key) or - * custom token provider. - * @param isTokenAutoRefreshEnabled If true, the SDK automatically - * refreshes App Check tokens as needed. If undefined, defaults to the - * value of `app.automaticDataCollectionEnabled`, which defaults to - * false and can be set in the app config. - */ - activate( - siteKeyOrProvider: string | AppCheckProvider, - isTokenAutoRefreshEnabled?: boolean - ): void; - - /** - * - * @param isTokenAutoRefreshEnabled If true, the SDK automatically - * refreshes App Check tokens as needed. This overrides any value set - * during `activate()`. - */ - setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled: boolean): void; - /** - * Get the current App Check token. Attaches to the most recent - * in-flight request if one is present. Returns null if no token - * is present and no token requests are in-flight. - * - * @param forceRefresh - If true, will always try to fetch a fresh token. - * If false, will use a cached token if found in storage. - */ - getToken( - forceRefresh?: boolean - ): Promise; - - /** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * @param observer An object with `next`, `error`, and `complete` - * properties. `next` is called with an - * {@link firebase.appCheck.AppCheckTokenResult `AppCheckTokenResult`} - * whenever the token changes. `error` is optional and is called if an - * error is thrown by the listener (the `next` function). `complete` - * is unused, as the token stream is unending. - * - * @returns A function that unsubscribes this listener. - */ - onTokenChanged(observer: { - next: (tokenResult: firebase.appCheck.AppCheckTokenResult) => void; - error?: (error: Error) => void; - complete?: () => void; - }): Unsubscribe; - - /** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * @param onNext When the token changes, this function is called with aa - * {@link firebase.appCheck.AppCheckTokenResult `AppCheckTokenResult`}. - * @param onError Optional. Called if there is an error thrown by the - * listener (the `onNext` function). - * @param onCompletion Currently unused, as the token stream is unending. - * @returns A function that unsubscribes this listener. - */ - onTokenChanged( - onNext: (tokenResult: firebase.appCheck.AppCheckTokenResult) => void, - onError?: (error: Error) => void, - onCompletion?: () => void - ): Unsubscribe; - } - - /** - * An App Check provider. This can be either the built-in reCAPTCHA - * provider or a custom provider. For more on custom providers, see - * https://firebase.google.com/docs/app-check/web-custom-provider - */ - interface AppCheckProvider { - /** - * Returns an AppCheck token. - */ - getToken(): Promise; - } - - /** - * The token returned from an {@link firebase.appCheck.AppCheckProvider `AppCheckProvider`}. - */ - interface AppCheckToken { - /** - * The token string in JWT format. - */ - readonly token: string; - /** - * The local timestamp after which the token will expire. - */ - readonly expireTimeMillis: number; - } -} - -/** - * @webonly - */ -declare namespace firebase.installations { - /** - * The Firebase Installations service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.installations `firebase.installations()`}. - */ - export interface Installations { - /** - * The {@link firebase.app.App app} associated with the `Installations` service - * instance. - * - * @example - * ```javascript - * var app = analytics.app; - * ``` - */ - app: firebase.app.App; - /** - * Creates a Firebase Installation if there isn't one for the app and - * returns the Installation ID. - * - * @return Firebase Installation ID - */ - getId(): Promise; - - /** - * Returns an Authentication Token for the current Firebase Installation. - * - * @return Firebase Installation Authentication Token - */ - getToken(forceRefresh?: boolean): Promise; - - /** - * Deletes the Firebase Installation and all associated data. - */ - delete(): Promise; - - /** - * Sets a new callback that will get called when Installlation ID changes. - * Returns an unsubscribe function that will remove the callback when called. - */ - onIdChange(callback: (installationId: string) => void): () => void; - } -} - -/** - * @webonly - */ -declare namespace firebase.performance { - /** - * The Firebase Performance Monitoring service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.performance `firebase.performance()`}. - */ - export interface Performance { - /** - * The {@link firebase.app.App app} associated with the `Performance` service - * instance. - * - * @example - * ```javascript - * var app = analytics.app; - * ``` - */ - app: firebase.app.App; - /** - * Creates an uninitialized instance of {@link firebase.performance.Trace `trace`} and returns - * it. - * - * @param traceName The name of the trace instance. - * @return The Trace instance. - */ - trace(traceName: string): Trace; - - /** - * Controls the logging of automatic traces and HTTP/S network monitoring. - */ - instrumentationEnabled: boolean; - /** - * Controls the logging of custom traces. - */ - dataCollectionEnabled: boolean; - } - - export interface Trace { - /** - * Starts the timing for the {@link firebase.performance.Trace `trace`} instance. - */ - start(): void; - /** - * Stops the timing of the {@link firebase.performance.Trace `trace`} instance and logs the - * data of the instance. - */ - stop(): void; - /** - * Records a {@link firebase.performance.Trace `trace`} from given parameters. This provides a - * direct way to use {@link firebase.performance.Trace `trace`} without a need to start/stop. - * This is useful for use cases in which the {@link firebase.performance.Trace `trace`} cannot - * directly be used (e.g. if the duration was captured before the Performance SDK was loaded). - * - * @param startTime Trace start time since epoch in millisec. - * @param duration The duraction of the trace in millisec. - * @param options An object which can optionally hold maps of custom metrics and - * custom attributes. - */ - record( - startTime: number, - duration: number, - options?: { - metrics?: { [key: string]: number }; - attributes?: { [key: string]: string }; - } - ): void; - /** - * Adds to the value of a custom metric. If a custom metric with the provided name does not - * exist, it creates one with that name and the value equal to the given number. - * - * @param metricName The name of the custom metric. - * @param num The number to be added to the value of the custom metric. If not provided, it - * uses a default value of one. - */ - incrementMetric(metricName: string, num?: number): void; - /** - * Sets the value of the specified custom metric to the given number regardless of whether - * a metric with that name already exists on the {@link firebase.performance.Trace `trace`} - * instance or not. - * - * @param metricName Name of the custom metric. - * @param num Value to of the custom metric. - */ - putMetric(metricName: string, num: number): void; - /** - * Returns the value of the custom metric by that name. If a custom metric with that name does - * not exist returns zero. - * - * @param metricName Name of the custom metric. - */ - getMetric(metricName: string): number; - /** - * Set a custom attribute of a {@link firebase.performance.Trace `trace`} to a certain value. - * - * @param attr Name of the custom attribute. - * @param value Value of the custom attribute. - */ - putAttribute(attr: string, value: string): void; - /** - * Retrieves the value that the custom attribute is set to. - * - * @param attr Name of the custom attribute. - */ - getAttribute(attr: string): string | undefined; - /** - * Removes the specified custom attribute from a {@link firebase.performance.Trace `trace`} - * instance. - * - * @param attr Name of the custom attribute. - */ - - removeAttribute(attr: string): void; - /** - * Returns a map of all custom attributes of a {@link firebase.performance.Trace `trace`} - * instance. - */ - getAttributes(): { [key: string]: string }; - } -} - -/** - * @webonly - */ -declare namespace firebase.remoteConfig { - /** - * The Firebase Remote Config service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.remoteConfig `firebase.remoteConfig()`}. - */ - export interface RemoteConfig { - /** - * The {@link firebase.app.App app} associated with the `Performance` service - * instance. - * - * @example - * ```javascript - * var app = analytics.app; - * ``` - */ - app: firebase.app.App; - /** - * Defines configuration for the Remote Config SDK. - */ - settings: Settings; - - /** - * Object containing default values for conigs. - */ - defaultConfig: { [key: string]: string | number | boolean }; - - /** - * The Unix timestamp in milliseconds of the last successful fetch, or negative one if - * the {@link RemoteConfig} instance either hasn't fetched or initialization - * is incomplete. - */ - fetchTimeMillis: number; - - /** - * The status of the last fetch attempt. - */ - lastFetchStatus: FetchStatus; - - /** - * Makes the last fetched config available to the getters. - * Returns a promise which resolves to true if the current call activated the fetched configs. - * If the fetched configs were already activated, the promise will resolve to false. - */ - activate(): Promise; - - /** - * Ensures the last activated config are available to the getters. - */ - ensureInitialized(): Promise; - - /** - * Fetches and caches configuration from the Remote Config service. - */ - fetch(): Promise; - - /** - * Performs fetch and activate operations, as a convenience. - * Returns a promise which resolves to true if the current call activated the fetched configs. - * If the fetched configs were already activated, the promise will resolve to false. - */ - fetchAndActivate(): Promise; - - /** - * Gets all config. - */ - getAll(): { [key: string]: Value }; - - /** - * Gets the value for the given key as a boolean. - * - * Convenience method for calling remoteConfig.getValue(key).asBoolean(). - */ - getBoolean(key: string): boolean; - - /** - * Gets the value for the given key as a number. - * - * Convenience method for calling remoteConfig.getValue(key).asNumber(). - */ - getNumber(key: string): number; - - /** - * Gets the value for the given key as a String. - * - * Convenience method for calling remoteConfig.getValue(key).asString(). - */ - getString(key: string): string; - - /** - * Gets the {@link Value} for the given key. - */ - getValue(key: string): Value; - - /** - * Defines the log level to use. - */ - setLogLevel(logLevel: LogLevel): void; - } - - /** - * Indicates the source of a value. - * - *
    - *
  • "static" indicates the value was defined by a static constant.
  • - *
  • "default" indicates the value was defined by default config.
  • - *
  • "remote" indicates the value was defined by fetched config.
  • - *
- */ - export type ValueSource = 'static' | 'default' | 'remote'; - - /** - * Wraps a value with metadata and type-safe getters. - */ - export interface Value { - /** - * Gets the value as a boolean. - * - * The following values (case insensitive) are interpreted as true: - * "1", "true", "t", "yes", "y", "on". Other values are interpreted as false. - */ - asBoolean(): boolean; - - /** - * Gets the value as a number. Comparable to calling Number(value) || 0. - */ - asNumber(): number; - - /** - * Gets the value as a string. - */ - asString(): string; - - /** - * Gets the {@link ValueSource} for the given key. - */ - getSource(): ValueSource; - } - - /** - * Defines configuration options for the Remote Config SDK. - */ - export interface Settings { - /** - * Defines the maximum age in milliseconds of an entry in the config cache before - * it is considered stale. Defaults to 43200000 (Twelve hours). - */ - minimumFetchIntervalMillis: number; - - /** - * Defines the maximum amount of milliseconds to wait for a response when fetching - * configuration from the Remote Config server. Defaults to 60000 (One minute). - */ - fetchTimeoutMillis: number; - } - - /** - * Summarizes the outcome of the last attempt to fetch config from the Firebase Remote Config server. - * - *
    - *
  • "no-fetch-yet" indicates the {@link RemoteConfig} instance has not yet attempted - * to fetch config, or that SDK initialization is incomplete.
  • - *
  • "success" indicates the last attempt succeeded.
  • - *
  • "failure" indicates the last attempt failed.
  • - *
  • "throttle" indicates the last attempt was rate-limited.
  • - *
- */ - export type FetchStatus = 'no-fetch-yet' | 'success' | 'failure' | 'throttle'; - - /** - * Defines levels of Remote Config logging. - */ - export type LogLevel = 'debug' | 'error' | 'silent'; -} - -declare namespace firebase.functions { - /** - * An HttpsCallableResult wraps a single result from a function call. - */ - export interface HttpsCallableResult { - readonly data: any; - } - /** - * An HttpsCallable is a reference to a "callable" http trigger in - * Google Cloud Functions. - */ - export interface HttpsCallable { - (data?: any): Promise; - } - export interface HttpsCallableOptions { - timeout?: number; - } - /** - * The Cloud Functions for Firebase service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.functions `firebase.functions()`}. - */ - export class Functions { - private constructor(); - - /** - * Modify this instance to communicate with the Cloud Functions emulator. - * - * Note: this must be called before this instance has been used to do any operations. - * - * @param host The emulator host (ex: localhost) - * @param port The emulator port (ex: 5001) - */ - useEmulator(host: string, port: number): void; - - /** - * Changes this instance to point to a Cloud Functions emulator running - * locally. See https://firebase.google.com/docs/functions/local-emulator - * - * @deprecated Prefer the useEmulator(host, port) method. - * @param origin The origin of the local emulator, such as - * "http://localhost:5005". - */ - useFunctionsEmulator(url: string): void; - /** - * Gets an `HttpsCallable` instance that refers to the function with the given - * name. - * - * @param name The name of the https callable function. - * @param options The options for this HttpsCallable instance. - * @return The `HttpsCallable` instance. - */ - httpsCallable(name: string, options?: HttpsCallableOptions): HttpsCallable; - } - /** - * The set of Firebase Functions status codes. The codes are the same at the - * ones exposed by gRPC here: - * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - * - * Possible values: - * - 'cancelled': The operation was cancelled (typically by the caller). - * - 'unknown': Unknown error or an error from a different error domain. - * - 'invalid-argument': Client specified an invalid argument. Note that this - * differs from 'failed-precondition'. 'invalid-argument' indicates - * arguments that are problematic regardless of the state of the system - * (e.g. an invalid field name). - * - 'deadline-exceeded': Deadline expired before operation could complete. - * For operations that change the state of the system, this error may be - * returned even if the operation has completed successfully. For example, - * a successful response from a server could have been delayed long enough - * for the deadline to expire. - * - 'not-found': Some requested document was not found. - * - 'already-exists': Some document that we attempted to create already - * exists. - * - 'permission-denied': The caller does not have permission to execute the - * specified operation. - * - 'resource-exhausted': Some resource has been exhausted, perhaps a - * per-user quota, or perhaps the entire file system is out of space. - * - 'failed-precondition': Operation was rejected because the system is not - * in a state required for the operation's execution. - * - 'aborted': The operation was aborted, typically due to a concurrency - * issue like transaction aborts, etc. - * - 'out-of-range': Operation was attempted past the valid range. - * - 'unimplemented': Operation is not implemented or not supported/enabled. - * - 'internal': Internal errors. Means some invariants expected by - * underlying system has been broken. If you see one of these errors, - * something is very broken. - * - 'unavailable': The service is currently unavailable. This is most likely - * a transient condition and may be corrected by retrying with a backoff. - * - 'data-loss': Unrecoverable data loss or corruption. - * - 'unauthenticated': The request does not have valid authentication - * credentials for the operation. - */ - export type FunctionsErrorCode = - | 'ok' - | 'cancelled' - | 'unknown' - | 'invalid-argument' - | 'deadline-exceeded' - | 'not-found' - | 'already-exists' - | 'permission-denied' - | 'resource-exhausted' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'unimplemented' - | 'internal' - | 'unavailable' - | 'data-loss' - | 'unauthenticated'; - export interface HttpsError extends Error { - /** - * A standard error code that will be returned to the client. This also - * determines the HTTP status code of the response, as defined in code.proto. - */ - readonly code: FunctionsErrorCode; - /** - * Extra data to be converted to JSON and included in the error response. - */ - readonly details?: any; - } -} - -declare namespace firebase.auth { - /** - * A utility class to parse email action URLs. - */ - class ActionCodeURL { - private constructor(); - /** - * The API key of the email action link. - */ - apiKey: string; - /** - * The action code of the email action link. - */ - code: string; - /** - * The continue URL of the email action link. Null if not provided. - */ - continueUrl: string | null; - /** - * The language code of the email action link. Null if not provided. - */ - languageCode: string | null; - /** - * The action performed by the email action link. It returns from one - * of the types from {@link firebase.auth.ActionCodeInfo}. - */ - operation: firebase.auth.ActionCodeInfo.Operation; - /** - * Parses the email action link string and returns an ActionCodeURL object - * if the link is valid, otherwise returns null. - * - * @param link The email action link string. - * @return The ActionCodeURL object, or null if the link is invalid. - */ - static parseLink(link: string): firebase.auth.ActionCodeURL | null; - /** - * The tenant ID of the email action link. Null if the email action - * is from the parent project. - */ - tenantId: string | null; - } - /** - * A response from {@link firebase.auth.Auth.checkActionCode}. - */ - interface ActionCodeInfo { - /** - * The data associated with the action code. - * - * For the `PASSWORD_RESET`, `VERIFY_EMAIL`, and `RECOVER_EMAIL` actions, this object - * contains an `email` field with the address the email was sent to. - * - * For the RECOVER_EMAIL action, which allows a user to undo an email address - * change, this object also contains a `previousEmail` field with the user account's - * current email address. After the action completes, the user's email address will - * revert to the value in the `email` field from the value in `previousEmail` field. - * - * For the VERIFY_AND_CHANGE_EMAIL action, which allows a user to verify the email - * before updating it, this object contains a `previousEmail` field with the user - * account's email address before updating. After the action completes, the user's - * email address will be updated to the value in the `email` field from the value - * in `previousEmail` field. - * - * For the REVERT_SECOND_FACTOR_ADDITION action, which allows a user to unenroll - * a newly added second factor, this object contains a `multiFactorInfo` field with - * the information about the second factor. For phone second factor, the - * `multiFactorInfo` is a {@link firebase.auth.PhoneMultiFactorInfo} object, - * which contains the phone number. - */ - data: { - email?: string | null; - /** - * @deprecated - * This field is deprecated in favor of previousEmail. - */ - fromEmail?: string | null; - multiFactorInfo?: firebase.auth.MultiFactorInfo | null; - previousEmail?: string | null; - }; - /** - * The type of operation that generated the action code. This could be: - *
    - *
  • `EMAIL_SIGNIN`: email sign in code generated via - * {@link firebase.auth.Auth.sendSignInLinkToEmail}.
  • - *
  • `PASSWORD_RESET`: password reset code generated via - * {@link firebase.auth.Auth.sendPasswordResetEmail}.
  • - *
  • `RECOVER_EMAIL`: email change revocation code generated via - * {@link firebase.User.updateEmail}.
  • - *
  • `REVERT_SECOND_FACTOR_ADDITION`: revert second factor addition - * code generated via - * {@link firebase.User.MultiFactorUser.enroll}.
  • - *
  • `VERIFY_AND_CHANGE_EMAIL`: verify and change email code generated - * via {@link firebase.User.verifyBeforeUpdateEmail}.
  • - *
  • `VERIFY_EMAIL`: email verification code generated via - * {@link firebase.User.sendEmailVerification}.
  • - *
- */ - operation: string; - } - - /** - * This is the interface that defines the required continue/state URL with - * optional Android and iOS bundle identifiers. - * The action code setting fields are: - *
    - *
  • url: Sets the link continue/state URL, which has different meanings - * in different contexts:

    - *
      - *
    • When the link is handled in the web action widgets, this is the deep - * link in the continueUrl query parameter.
    • - *
    • When the link is handled in the app directly, this is the continueUrl - * query parameter in the deep link of the Dynamic Link.
    • - *
    - *
  • - *
  • iOS: Sets the iOS bundle ID. This will try to open the link in an iOS app - * if it is installed.
  • - *
  • android: Sets the Android package name. This will try to open the link in - * an android app if it is installed. If installApp is passed, it specifies - * whether to install the Android app if the device supports it and the app - * is not already installed. If this field is provided without a - * packageName, an error is thrown explaining that the packageName must be - * provided in conjunction with this field. - * If minimumVersion is specified, and an older version of the app is - * installed, the user is taken to the Play Store to upgrade the app.
  • - *
  • handleCodeInApp: The default is false. When set to true, the action code - * link will be be sent as a Universal Link or Android App Link and will be - * opened by the app if installed. In the false case, the code will be sent - * to the web widget first and then on continue will redirect to the app if - * installed.
  • - *
- */ - type ActionCodeSettings = { - android?: { - installApp?: boolean; - minimumVersion?: string; - packageName: string; - }; - handleCodeInApp?: boolean; - iOS?: { bundleId: string }; - url: string; - dynamicLinkDomain?: string; - }; - - /** - * A structure containing additional user information from a federated identity - * provider. - */ - type AdditionalUserInfo = { - isNewUser: boolean; - profile: Object | null; - providerId: string; - username?: string | null; - }; - - /** - * A verifier for domain verification and abuse prevention. Currently, the - * only implementation is {@link firebase.auth.RecaptchaVerifier}. - */ - interface ApplicationVerifier { - /** - * Identifies the type of application verifier (e.g. "recaptcha"). - */ - type: string; - /** - * Executes the verification process. - * @return A Promise for a token that can be used to - * assert the validity of a request. - */ - verify(): Promise; - } - - /** - * Interface representing an Auth instance's settings, currently used for - * enabling/disabling app verification for phone Auth testing. - */ - interface AuthSettings { - /** - * When set, this property disables app verification for the purpose of testing - * phone authentication. For this property to take effect, it needs to be set - * before rendering a reCAPTCHA app verifier. When this is disabled, a - * mock reCAPTCHA is rendered instead. This is useful for manual testing during - * development or for automated integration tests. - * - * In order to use this feature, you will need to - * {@link https://firebase.google.com/docs/auth/web/phone-auth#test-with-whitelisted-phone-numbers - * whitelist your phone number} via the - * Firebase Console. - * - * The default value is false (app verification is enabled). - */ - appVerificationDisabledForTesting: boolean; - } - /** - * Interface representing the Auth config. - * - * @public - */ - export interface Config { - /** - * The API Key used to communicate with the Firebase Auth backend. - */ - apiKey: string; - /** - * The host at which the Firebase Auth backend is running. - */ - apiHost: string; - /** - * The scheme used to communicate with the Firebase Auth backend. - */ - apiScheme: string; - /** - * The host at which the Secure Token API is running. - */ - tokenApiHost: string; - /** - * The SDK Client Version. - */ - sdkClientVersion: string; - /** - * The domain at which the web widgets are hosted (provided via Firebase Config). - */ - authDomain?: string; - } - - /** - * Configuration of Firebase Authentication Emulator. - */ - export interface EmulatorConfig { - /** - * The protocol used to communicate with the emulator ("http"/"https"). - */ - readonly protocol: string; - /** - * The hostname of the emulator, which may be a domain ("localhost"), IPv4 address ("127.0.0.1") - * or quoted IPv6 address ("[::1]"). - */ - readonly host: string; - /** - * The port of the emulator, or null if port isn't specified (i.e. protocol default). - */ - readonly port: number | null; - /** - * The emulator-specific options. - */ - readonly options: { - /** - * Whether the warning banner attached to the DOM was disabled. - */ - readonly disableWarnings: boolean; - }; - } - /** - * The Firebase Auth service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.auth `firebase.auth()`}. - * - * See - * {@link https://firebase.google.com/docs/auth/ Firebase Authentication} - * for a full guide on how to use the Firebase Auth service. - * - */ - interface Auth { - /** The name of the app associated with the Auth service instance. */ - readonly name: string; - /** The config used to initialize this instance. */ - readonly config: Config; - /** The current emulator configuration (or null). */ - readonly emulatorConfig: EmulatorConfig | null; - /** - * The {@link firebase.app.App app} associated with the `Auth` service - * instance. - * - * @example - * ```javascript - * var app = auth.app; - * ``` - */ - app: firebase.app.App; - /** - * Applies a verification code sent to the user by email or other out-of-band - * mechanism. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if the action code has expired.
- *
auth/invalid-action-code
- *
Thrown if the action code is invalid. This can happen if the code is - * malformed or has already been used.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given action code has been - * disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the action code. This may - * have happened if the user was deleted between when the action code was - * issued and when this method was called.
- *
- * - * @param code A verification code sent to the user. - */ - applyActionCode(code: string): Promise; - /** - * Checks a verification code sent to the user by email or other out-of-band - * mechanism. - * - * Returns metadata about the code. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if the action code has expired.
- *
auth/invalid-action-code
- *
Thrown if the action code is invalid. This can happen if the code is - * malformed or has already been used.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given action code has been - * disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the action code. This may - * have happened if the user was deleted between when the action code was - * issued and when this method was called.
- *
- * - * @param code A verification code sent to the user. - */ - checkActionCode(code: string): Promise; - /** - * Completes the password reset process, given a confirmation code and new - * password. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if the password reset code has expired.
- *
auth/invalid-action-code
- *
Thrown if the password reset code is invalid. This can happen if the - * code is malformed or has already been used.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given password reset code has - * been disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the password reset code. This - * may have happened if the user was deleted between when the code was - * issued and when this method was called.
- *
auth/weak-password
- *
Thrown if the new password is not strong enough.
- *
- * - * @param code The confirmation code send via email to the user. - * @param newPassword The new password. - */ - confirmPasswordReset(code: string, newPassword: string): Promise; - - /** - * Creates a new user account associated with the specified email address and - * password. - * - * On successful creation of the user account, this user will also be - * signed in to your application. - * - * User account creation can fail if the account already exists or the password - * is invalid. - * - * Note: The email address acts as a unique identifier for the user and - * enables an email-based password reset. This function will create - * a new user account and set the initial user password. - * - *

Error Codes

- *
- *
auth/email-already-in-use
- *
Thrown if there already exists an account with the given email - * address.
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/operation-not-allowed
- *
Thrown if email/password accounts are not enabled. Enable email/password - * accounts in the Firebase Console, under the Auth tab.
- *
auth/weak-password
- *
Thrown if the password is not strong enough.
- *
- * - * @example - * ```javascript - * firebase.auth().createUserWithEmailAndPassword(email, password) - * .catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * if (errorCode == 'auth/weak-password') { - * alert('The password is too weak.'); - * } else { - * alert(errorMessage); - * } - * console.log(error); - * }); - * ``` - * @param email The user's email address. - * @param password The user's chosen password. - */ - createUserWithEmailAndPassword( - email: string, - password: string - ): Promise; - /** - * The currently signed-in user (or null). - */ - currentUser: firebase.User | null; - - /** - * Gets the list of possible sign in methods for the given email address. This - * is useful to differentiate methods of sign-in for the same provider, - * eg. `EmailAuthProvider` which has 2 methods of sign-in, email/password and - * email/link. - * - *

Error Codes

- *
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
- */ - fetchSignInMethodsForEmail(email: string): Promise>; - - /** - * Checks if an incoming link is a sign-in with email link. - */ - isSignInWithEmailLink(emailLink: string): boolean; - /** - * Returns a UserCredential from the redirect-based sign-in flow. - * - * If sign-in succeeded, returns the signed in user. If sign-in was - * unsuccessful, fails with an error. If no redirect operation was called, - * returns a UserCredential with a null User. - * - *

Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail} with the error.email - * and then asking the user to sign in using one of the returned providers. - * Once the user is signed in, the original credential retrieved from the - * error.credential can be linked to the user with - * {@link firebase.User.linkWithCredential} to prevent the user from signing - * in again to the original provider via popup or redirect. If you are using - * redirects for sign in, save the credential in session storage and then - * retrieve on redirect and repopulate the credential using for example - * {@link firebase.auth.GoogleAuthProvider.credential} depending on the - * credential provider id and complete the link.
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * An error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. You can - * recover from this error by signing in with that credential directly via - * {@link firebase.auth.Auth.signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User.linkWithCredential} the original credential to that - * newly signed in user.
- *
auth/operation-not-allowed
- *
Thrown if the type of account corresponding to the credential - * is not enabled. Enable the account type in the Firebase Console, under - * the Auth tab.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/timeout
- *
Thrown typically if the app domain is not authorized for OAuth operations - * for your Firebase project. Edit the list of authorized domains from the - * Firebase console.
- *
- * - * @webonly - * - * @example - * ```javascript - * // First, we perform the signInWithRedirect. - * // Creates the provider object. - * var provider = new firebase.auth.FacebookAuthProvider(); - * // You can add additional scopes to the provider: - * provider.addScope('email'); - * provider.addScope('user_friends'); - * // Sign in with redirect: - * auth.signInWithRedirect(provider) - * //////////////////////////////////////////////////////////// - * // The user is redirected to the provider's sign in flow... - * //////////////////////////////////////////////////////////// - * // Then redirected back to the app, where we check the redirect result: - * auth.getRedirectResult().then(function(result) { - * // The firebase.User instance: - * var user = result.user; - * // The Facebook firebase.auth.AuthCredential containing the Facebook - * // access token: - * var credential = result.credential; - * // As this API can be used for sign-in, linking and reauthentication, - * // check the operationType to determine what triggered this redirect - * // operation. - * var operationType = result.operationType; - * }, function(error) { - * // The provider's account email, can be used in case of - * // auth/account-exists-with-different-credential to fetch the providers - * // linked to the email: - * var email = error.email; - * // The provider's credential: - * var credential = error.credential; - * // In case of auth/account-exists-with-different-credential error, - * // you can fetch the providers using this: - * if (error.code === 'auth/account-exists-with-different-credential') { - * auth.fetchSignInMethodsForEmail(email).then(function(providers) { - * // The returned 'providers' is a list of the available providers - * // linked to the email address. Please refer to the guide for a more - * // complete explanation on how to recover from this error. - * }); - * } - * }); - * ``` - */ - getRedirectResult(): Promise; - /** - * The current Auth instance's language code. This is a readable/writable - * property. When set to null, the default Firebase Console language setting - * is applied. The language code will propagate to email action templates - * (password reset, email verification and email change revocation), SMS - * templates for phone authentication, reCAPTCHA verifier and OAuth - * popup/redirect operations provided the specified providers support - * localization with the language code specified. - */ - languageCode: string | null; - /** - * The current Auth instance's settings. This is used to edit/read configuration - * related options like app verification mode for phone authentication. - */ - settings: firebase.auth.AuthSettings; - /** - * Adds an observer for changes to the user's sign-in state. - * - * Prior to 4.0.0, this triggered the observer when users were signed in, - * signed out, or when the user's ID token changed in situations such as token - * expiry or password change. After 4.0.0, the observer is only triggered - * on sign-in or sign-out. - * - * To keep the old behavior, see {@link firebase.auth.Auth.onIdTokenChanged}. - * - * @example - * ```javascript - * firebase.auth().onAuthStateChanged(function(user) { - * if (user) { - * // User is signed in. - * } - * }); - * ``` - */ - onAuthStateChanged( - nextOrObserver: - | firebase.Observer - | ((a: firebase.User | null) => any), - error?: (a: firebase.auth.Error) => any, - completed?: firebase.Unsubscribe - ): firebase.Unsubscribe; - /** - * Adds an observer for changes to the signed-in user's ID token, which includes - * sign-in, sign-out, and token refresh events. This method has the same - * behavior as {@link firebase.auth.Auth.onAuthStateChanged} had prior to 4.0.0. - * - * @example - * ```javascript - * firebase.auth().onIdTokenChanged(function(user) { - * if (user) { - * // User is signed in or token was refreshed. - * } - * }); - * ``` - * @param - * nextOrObserver An observer object or a function triggered on change. - * @param error Optional A function - * triggered on auth error. - * @param completed Optional A function triggered when the - * observer is removed. - */ - onIdTokenChanged( - nextOrObserver: - | firebase.Observer - | ((a: firebase.User | null) => any), - error?: (a: firebase.auth.Error) => any, - completed?: firebase.Unsubscribe - ): firebase.Unsubscribe; - /** - * Sends a sign-in email link to the user with the specified email. - * - * The sign-in operation has to always be completed in the app unlike other out - * of band email actions (password reset and email verifications). This is - * because, at the end of the flow, the user is expected to be signed in and - * their Auth state persisted within the app. - * - * To complete sign in with the email link, call - * {@link firebase.auth.Auth.signInWithEmailLink} with the email address and - * the email link supplied in the email sent to the user. - * - *

Error Codes

- *
- *
auth/argument-error
- *
Thrown if handleCodeInApp is false.
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/missing-android-pkg-name
- *
An Android package name must be provided if the Android app is required - * to be installed.
- *
auth/missing-continue-uri
- *
A continue URL must be provided in the request.
- *
auth/missing-ios-bundle-id
- *
An iOS Bundle ID must be provided if an App Store ID is provided.
- *
auth/invalid-continue-uri
- *
The continue URL provided in the request is invalid.
- *
auth/unauthorized-continue-uri
- *
The domain of the continue URL is not whitelisted. Whitelist - * the domain in the Firebase console.
- *
- * - * @example - * ```javascript - * var actionCodeSettings = { - * // The URL to redirect to for sign-in completion. This is also the deep - * // link for mobile redirects. The domain (www.example.com) for this URL - * // must be whitelisted in the Firebase Console. - * url: 'https://www.example.com/finishSignUp?cartId=1234', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * // This must be true. - * handleCodeInApp: true - * }; - * firebase.auth().sendSignInLinkToEmail('user@example.com', actionCodeSettings) - * .then(function() { - * // The link was successfully sent. Inform the user. Save the email - * // locally so you don't need to ask the user for it again if they open - * // the link on the same device. - * }) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * }); - * ``` - * @param email The email account to sign in with. - * @param actionCodeSettings The action - * code settings. The action code settings which provides Firebase with - * instructions on how to construct the email link. This includes the - * sign in completion URL or the deep link for mobile redirects, the mobile - * apps to use when the sign-in link is opened on an Android or iOS device. - * Mobile app redirects will only be applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of condition. - * The Android package name and iOS bundle ID will be respected only if they - * are configured in the same Firebase Auth project used. - */ - sendSignInLinkToEmail( - email: string, - actionCodeSettings: firebase.auth.ActionCodeSettings - ): Promise; - - /** - * Sends a password reset email to the given email address. - * - * To complete the password reset, call - * {@link firebase.auth.Auth.confirmPasswordReset} with the code supplied in the - * email sent to the user, along with the new password specified by the user. - * - *

Error Codes

- *
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/missing-android-pkg-name
- *
An Android package name must be provided if the Android app is required - * to be installed.
- *
auth/missing-continue-uri
- *
A continue URL must be provided in the request.
- *
auth/missing-ios-bundle-id
- *
An iOS Bundle ID must be provided if an App Store ID is provided.
- *
auth/invalid-continue-uri
- *
The continue URL provided in the request is invalid.
- *
auth/unauthorized-continue-uri
- *
The domain of the continue URL is not whitelisted. Whitelist - * the domain in the Firebase console.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the email address.
- *
- * - * @example - * ```javascript - * var actionCodeSettings = { - * url: 'https://www.example.com/?email=user@example.com', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * handleCodeInApp: true - * }; - * firebase.auth().sendPasswordResetEmail( - * 'user@example.com', actionCodeSettings) - * .then(function() { - * // Password reset email sent. - * }) - * .catch(function(error) { - * // Error occurred. Inspect error.code. - * }); - * ``` - * - * @param email The email address with the password to be reset. - * @param actionCodeSettings The action - * code settings. If specified, the state/continue URL will be set as the - * "continueUrl" parameter in the password reset link. The default password - * reset landing page will use this to display a link to go back to the app - * if it is installed. - * If the actionCodeSettings is not specified, no URL is appended to the - * action URL. - * The state URL provided must belong to a domain that is whitelisted by the - * developer in the console. Otherwise an error will be thrown. - * Mobile app redirects will only be applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of condition. - * The Android package name and iOS bundle ID will be respected only if they - * are configured in the same Firebase Auth project used. - */ - sendPasswordResetEmail( - email: string, - actionCodeSettings?: firebase.auth.ActionCodeSettings | null - ): Promise; - - /** - * Changes the current type of persistence on the current Auth instance for the - * currently saved Auth session and applies this type of persistence for - * future sign-in requests, including sign-in with redirect requests. This will - * return a promise that will resolve once the state finishes copying from one - * type of storage to the other. - * Calling a sign-in method after changing persistence will wait for that - * persistence change to complete before applying it on the new Auth state. - * - * This makes it easy for a user signing in to specify whether their session - * should be remembered or not. It also makes it easier to never persist the - * Auth state for applications that are shared by other users or have sensitive - * data. - * - * The default for web browser apps and React Native apps is 'local' (provided - * the browser supports this mechanism) whereas it is 'none' for Node.js backend - * apps. - * - *

Error Codes (thrown synchronously)

- *
- *
auth/invalid-persistence-type
- *
Thrown if the specified persistence type is invalid.
- *
auth/unsupported-persistence-type
- *
Thrown if the current environment does not support the specified - * persistence type.
- *
- * - * @example - * ```javascript - * firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION) - * .then(function() { - * // Existing and future Auth states are now persisted in the current - * // session only. Closing the window would clear any existing state even if - * // a user forgets to sign out. - * }); - * ``` - */ - setPersistence(persistence: firebase.auth.Auth.Persistence): Promise; - - /** - * Asynchronously signs in with the given credentials, and returns any available - * additional user information, such as user name. - * - *

Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail} and then asking the - * user to sign in using one of the returned providers. Once the user is - * signed in, the original credential can be linked to the user with - * {@link firebase.User.linkWithCredential}.
- *
auth/invalid-credential
- *
Thrown if the credential is malformed or has expired.
- *
auth/operation-not-allowed
- *
Thrown if the type of account corresponding to the credential - * is not enabled. Enable the account type in the Firebase Console, under - * the Auth tab.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given credential has been - * disabled.
- *
auth/user-not-found
- *
Thrown if signing in with a credential from - * {@link firebase.auth.EmailAuthProvider.credential} and there is no user - * corresponding to the given email.
- *
auth/wrong-password
- *
Thrown if signing in with a credential from - * {@link firebase.auth.EmailAuthProvider.credential} and the password is - * invalid for the given email, or if the account corresponding to the email - * does not have a password set.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
- * - * @deprecated - * This method is deprecated. Use - * {@link firebase.auth.Auth.signInWithCredential} instead. - * - * @example - * ```javascript - * firebase.auth().signInAndRetrieveDataWithCredential(credential) - * .then(function(userCredential) { - * console.log(userCredential.additionalUserInfo.username); - * }); - * ``` - * @param credential The auth credential. - */ - signInAndRetrieveDataWithCredential( - credential: firebase.auth.AuthCredential - ): Promise; - /** - * Asynchronously signs in as an anonymous user. - * - * - * If there is already an anonymous user signed in, that user will be returned; - * otherwise, a new anonymous user identity will be created and returned. - * - *

Error Codes

- *
- *
auth/operation-not-allowed
- *
Thrown if anonymous accounts are not enabled. Enable anonymous accounts - * in the Firebase Console, under the Auth tab.
- *
- * - * @example - * ```javascript - * firebase.auth().signInAnonymously().catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * - * if (errorCode === 'auth/operation-not-allowed') { - * alert('You must enable Anonymous auth in the Firebase Console.'); - * } else { - * console.error(error); - * } - * }); - * ``` - */ - signInAnonymously(): Promise; - - /** - * Asynchronously signs in with the given credentials. - * - *

Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail} and then asking the - * user to sign in using one of the returned providers. Once the user is - * signed in, the original credential can be linked to the user with - * {@link firebase.User.linkWithCredential}.
- *
auth/invalid-credential
- *
Thrown if the credential is malformed or has expired.
- *
auth/operation-not-allowed
- *
Thrown if the type of account corresponding to the credential - * is not enabled. Enable the account type in the Firebase Console, under - * the Auth tab.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given credential has been - * disabled.
- *
auth/user-not-found
- *
Thrown if signing in with a credential from - * {@link firebase.auth.EmailAuthProvider.credential} and there is no user - * corresponding to the given email.
- *
auth/wrong-password
- *
Thrown if signing in with a credential from - * {@link firebase.auth.EmailAuthProvider.credential} and the password is - * invalid for the given email, or if the account corresponding to the email - * does not have a password set.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
- * - * @example - * ```javascript - * firebase.auth().signInWithCredential(credential).catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * // The email of the user's account used. - * var email = error.email; - * // The firebase.auth.AuthCredential type that was used. - * var credential = error.credential; - * if (errorCode === 'auth/account-exists-with-different-credential') { - * alert('Email already associated with another account.'); - * // Handle account linking here, if using. - * } else { - * console.error(error); - * } - * }); - * ``` - * - * @param credential The auth credential. - */ - signInWithCredential( - credential: firebase.auth.AuthCredential - ): Promise; - /** - * Asynchronously signs in using a custom token. - * - * Custom tokens are used to integrate Firebase Auth with existing auth systems, - * and must be generated by the auth backend. - * - * Fails with an error if the token is invalid, expired, or not accepted by the - * Firebase Auth service. - * - *

Error Codes

- *
- *
auth/custom-token-mismatch
- *
Thrown if the custom token is for a different Firebase App.
- *
auth/invalid-custom-token
- *
Thrown if the custom token format is incorrect.
- *
- * - * @example - * ```javascript - * firebase.auth().signInWithCustomToken(token).catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * if (errorCode === 'auth/invalid-custom-token') { - * alert('The token you provided is not valid.'); - * } else { - * console.error(error); - * } - * }); - * ``` - * - * @param token The custom token to sign in with. - */ - signInWithCustomToken(token: string): Promise; - /** - * Asynchronously signs in using an email and password. - * - * Fails with an error if the email address and password do not match. - * - * Note: The user's password is NOT the password used to access the user's email - * account. The email address serves as a unique identifier for the user, and - * the password is used to access the user's account in your Firebase project. - * - * See also: {@link firebase.auth.Auth.createUserWithEmailAndPassword}. - * - *

Error Codes

- *
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given email has been - * disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the given email.
- *
auth/wrong-password
- *
Thrown if the password is invalid for the given email, or the account - * corresponding to the email does not have a password set.
- *
- * - * @example - * ```javascript - * firebase.auth().signInWithEmailAndPassword(email, password) - * .catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * if (errorCode === 'auth/wrong-password') { - * alert('Wrong password.'); - * } else { - * alert(errorMessage); - * } - * console.log(error); - * }); - * ``` - * - * @param email The users email address. - * @param password The users password. - */ - signInWithEmailAndPassword( - email: string, - password: string - ): Promise; - - /** - * Asynchronously signs in using a phone number. This method sends a code via - * SMS to the given phone number, and returns a - * {@link firebase.auth.ConfirmationResult}. After the user provides the code - * sent to their phone, call {@link firebase.auth.ConfirmationResult.confirm} - * with the code to sign the user in. - * - * For abuse prevention, this method also requires a - * {@link firebase.auth.ApplicationVerifier}. The Firebase Auth SDK includes - * a reCAPTCHA-based implementation, {@link firebase.auth.RecaptchaVerifier}. - * - *

Error Codes

- *
- *
auth/captcha-check-failed
- *
Thrown if the reCAPTCHA response token was invalid, expired, or if - * this method was called from a non-whitelisted domain.
- *
auth/invalid-phone-number
- *
Thrown if the phone number has an invalid format.
- *
auth/missing-phone-number
- *
Thrown if the phone number is missing.
- *
auth/quota-exceeded
- *
Thrown if the SMS quota for the Firebase project has been exceeded.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given phone number has been - * disabled.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
- * - * @example - * ```javascript - * // 'recaptcha-container' is the ID of an element in the DOM. - * var applicationVerifier = new firebase.auth.RecaptchaVerifier( - * 'recaptcha-container'); - * firebase.auth().signInWithPhoneNumber(phoneNumber, applicationVerifier) - * .then(function(confirmationResult) { - * var verificationCode = window.prompt('Please enter the verification ' + - * 'code that was sent to your mobile device.'); - * return confirmationResult.confirm(verificationCode); - * }) - * .catch(function(error) { - * // Handle Errors here. - * }); - * ``` - * - * @param phoneNumber The user's phone number in E.164 format (e.g. - * +16505550101). - * @param applicationVerifier - */ - signInWithPhoneNumber( - phoneNumber: string, - applicationVerifier: firebase.auth.ApplicationVerifier - ): Promise; - /** - * Asynchronously signs in using an email and sign-in email link. If no link - * is passed, the link is inferred from the current URL. - * - * Fails with an error if the email address is invalid or OTP in email link - * expires. - * - * Note: Confirm the link is a sign-in email link before calling this method - * {@link firebase.auth.Auth.isSignInWithEmailLink}. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if OTP in email link expires.
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given email has been - * disabled.
- *
- * - * @example - * ```javascript - * firebase.auth().signInWithEmailLink(email, emailLink) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * // Common errors could be invalid email and invalid or expired OTPs. - * }); - * ``` - * - * @param email The email account to sign in with. - * @param emailLink The optional link which contains the OTP needed - * to complete the sign in with email link. If not specified, the current - * URL is used instead. - */ - signInWithEmailLink( - email: string, - emailLink?: string - ): Promise; - /** - * Authenticates a Firebase client using a popup-based OAuth authentication - * flow. - * - * If succeeds, returns the signed in user along with the provider's credential. - * If sign in was unsuccessful, returns an error object containing additional - * information about the error. - * - *

Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail} with the error.email - * and then asking the user to sign in using one of the returned providers. - * Once the user is signed in, the original credential retrieved from the - * error.credential can be linked to the user with - * {@link firebase.User.linkWithCredential} to prevent the user from signing - * in again to the original provider via popup or redirect. If you are using - * redirects for sign in, save the credential in session storage and then - * retrieve on redirect and repopulate the credential using for example - * {@link firebase.auth.GoogleAuthProvider.credential} depending on the - * credential provider id and complete the link.
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/cancelled-popup-request
- *
Thrown if successive popup operations are triggered. Only one popup - * request is allowed at one time. All the popups would fail with this error - * except for the last one.
- *
auth/operation-not-allowed
- *
Thrown if the type of account corresponding to the credential - * is not enabled. Enable the account type in the Firebase Console, under - * the Auth tab.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/popup-blocked
- *
Thrown if the popup was blocked by the browser, typically when this - * operation is triggered outside of a click handler.
- *
auth/popup-closed-by-user
- *
Thrown if the popup window is closed by the user without completing the - * sign in to the provider.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @webonly - * - * @example - * ```javascript - * // Creates the provider object. - * var provider = new firebase.auth.FacebookAuthProvider(); - * // You can add additional scopes to the provider: - * provider.addScope('email'); - * provider.addScope('user_friends'); - * // Sign in with popup: - * auth.signInWithPopup(provider).then(function(result) { - * // The firebase.User instance: - * var user = result.user; - * // The Facebook firebase.auth.AuthCredential containing the Facebook - * // access token: - * var credential = result.credential; - * }, function(error) { - * // The provider's account email, can be used in case of - * // auth/account-exists-with-different-credential to fetch the providers - * // linked to the email: - * var email = error.email; - * // The provider's credential: - * var credential = error.credential; - * // In case of auth/account-exists-with-different-credential error, - * // you can fetch the providers using this: - * if (error.code === 'auth/account-exists-with-different-credential') { - * auth.fetchSignInMethodsForEmail(email).then(function(providers) { - * // The returned 'providers' is a list of the available providers - * // linked to the email address. Please refer to the guide for a more - * // complete explanation on how to recover from this error. - * }); - * } - * }); - * ``` - * - * @param provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - */ - signInWithPopup( - provider: firebase.auth.AuthProvider - ): Promise; - /** - * Authenticates a Firebase client using a full-page redirect flow. To handle - * the results and errors for this operation, refer to {@link - * firebase.auth.Auth.getRedirectResult}. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @webonly - * - * @param provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - */ - signInWithRedirect(provider: firebase.auth.AuthProvider): Promise; - /** - * Signs out the current user. - */ - signOut(): Promise; - /** - * The current Auth instance's tenant ID. This is a readable/writable - * property. When you set the tenant ID of an Auth instance, all future - * sign-in/sign-up operations will pass this tenant ID and sign in or - * sign up users to the specified tenant project. - * When set to null, users are signed in to the parent project. By default, - * this is set to null. - * - * @example - * ```javascript - * // Set the tenant ID on Auth instance. - * firebase.auth().tenantId = ‘TENANT_PROJECT_ID’; - * - * // All future sign-in request now include tenant ID. - * firebase.auth().signInWithEmailAndPassword(email, password) - * .then(function(result) { - * // result.user.tenantId should be ‘TENANT_PROJECT_ID’. - * }).catch(function(error) { - * // Handle error. - * }); - * ``` - */ - tenantId: string | null; - /** - * Asynchronously sets the provided user as `currentUser` on the current Auth - * instance. A new instance copy of the user provided will be made and set as - * `currentUser`. - * - * This will trigger {@link firebase.auth.Auth.onAuthStateChanged} and - * {@link firebase.auth.Auth.onIdTokenChanged} listeners like other sign in - * methods. - * - * The operation fails with an error if the user to be updated belongs to a - * different Firebase project. - * - *

Error Codes

- *
- *
auth/invalid-user-token
- *
Thrown if the user to be updated belongs to a diffent Firebase - * project.
- *
auth/user-token-expired
- *
Thrown if the token of the user to be updated is expired.
- *
auth/null-user
- *
Thrown if the user to be updated is null.
- *
auth/tenant-id-mismatch
- *
Thrown if the provided user's tenant ID does not match the - * underlying Auth instance's configured tenant ID
- *
- */ - updateCurrentUser(user: firebase.User | null): Promise; - /** - * Sets the current language to the default device/browser preference. - */ - useDeviceLanguage(): void; - /** - * Modify this Auth instance to communicate with the Firebase Auth emulator. This must be - * called synchronously immediately following the first call to `firebase.auth()`. Do not use - * with production credentials as emulator traffic is not encrypted. - * - * @param url The URL at which the emulator is running (eg, 'http://localhost:9099') - */ - useEmulator(url: string): void; - /** - * Checks a password reset code sent to the user by email or other out-of-band - * mechanism. - * - * Returns the user's email address if valid. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if the password reset code has expired.
- *
auth/invalid-action-code
- *
Thrown if the password reset code is invalid. This can happen if the code - * is malformed or has already been used.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given password reset code has - * been disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the password reset code. This - * may have happened if the user was deleted between when the code was - * issued and when this method was called.
- *
- * - * @param code A verification code sent to the user. - */ - verifyPasswordResetCode(code: string): Promise; - } - - /** - * Interface that represents the credentials returned by an auth provider. - * Implementations specify the details about each auth provider's credential - * requirements. - * - */ - abstract class AuthCredential { - /** - * The authentication provider ID for the credential. - * For example, 'facebook.com', or 'google.com'. - */ - providerId: string; - /** - * The authentication sign in method for the credential. - * For example, 'password', or 'emailLink. This corresponds to the sign-in - * method identifier as returned in - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}. - */ - signInMethod: string; - /** - * Returns a JSON-serializable representation of this object. - */ - toJSON(): Object; - /** - * Static method to deserialize a JSON representation of an object into an - * {@link firebase.auth.AuthCredential}. Input can be either Object or the - * stringified representation of the object. When string is provided, - * JSON.parse would be called first. If the JSON input does not represent - * an`AuthCredential`, null is returned. - * @param json The plain object representation of an - * AuthCredential. - */ - static fromJSON(json: Object | string): AuthCredential | null; - } - - /** - * Interface that represents the OAuth credentials returned by an OAuth - * provider. Implementations specify the details about each auth provider's - * credential requirements. - * - */ - class OAuthCredential extends AuthCredential { - private constructor(); - /** - * The OAuth ID token associated with the credential if it belongs to an - * OIDC provider, such as `google.com`. - */ - idToken?: string; - /** - * The OAuth access token associated with the credential if it belongs to - * an OAuth provider, such as `facebook.com`, `twitter.com`, etc. - */ - accessToken?: string; - /** - * The OAuth access token secret associated with the credential if it - * belongs to an OAuth 1.0 provider, such as `twitter.com`. - */ - secret?: string; - } - - /** - * Interface that represents an auth provider. - */ - interface AuthProvider { - providerId: string; - } - - /** - * A result from a phone number sign-in, link, or reauthenticate call. - */ - interface ConfirmationResult { - /** - * Finishes a phone number sign-in, link, or reauthentication, given the code - * that was sent to the user's mobile device. - * - *

Error Codes

- *
- *
auth/invalid-verification-code
- *
Thrown if the verification code is not valid.
- *
auth/missing-verification-code
- *
Thrown if the verification code is missing.
- *
- */ - confirm(verificationCode: string): Promise; - /** - * The phone number authentication operation's verification ID. This can be used - * along with the verification code to initialize a phone auth credential. - */ - verificationId: string; - } - - /** - * Email and password auth provider implementation. - * - * To authenticate: {@link firebase.auth.Auth.createUserWithEmailAndPassword} - * and {@link firebase.auth.Auth.signInWithEmailAndPassword}. - */ - class EmailAuthProvider extends EmailAuthProvider_Instance { - static PROVIDER_ID: string; - /** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}. - */ - static EMAIL_PASSWORD_SIGN_IN_METHOD: string; - /** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}. - */ - static EMAIL_LINK_SIGN_IN_METHOD: string; - /** - * @example - * ```javascript - * var cred = firebase.auth.EmailAuthProvider.credential( - * email, - * password - * ); - * ``` - * - * @param email Email address. - * @param password User account password. - * @return The auth provider credential. - */ - static credential( - email: string, - password: string - ): firebase.auth.AuthCredential; - /** - * Initialize an `EmailAuthProvider` credential using an email and an email link - * after a sign in with email link operation. - * - * @example - * ```javascript - * var cred = firebase.auth.EmailAuthProvider.credentialWithLink( - * email, - * emailLink - * ); - * ``` - * - * @param email Email address. - * @param emailLink Sign-in email link. - * @return The auth provider credential. - */ - static credentialWithLink( - email: string, - emailLink: string - ): firebase.auth.AuthCredential; - } - /** - * @hidden - */ - class EmailAuthProvider_Instance implements firebase.auth.AuthProvider { - providerId: string; - } - - /** - * An authentication error. - * For method-specific error codes, refer to the specific methods in the - * documentation. For common error codes, check the reference below. Use{@link - * firebase.auth.Error.code} to get the specific error code. For a detailed - * message, use {@link firebase.auth.Error.message}. - * Errors with the code auth/account-exists-with-different-credential - * will have the additional fields email and - * credential which are needed to provide a way to resolve these - * specific errors. Refer to {@link firebase.auth.Auth.signInWithPopup} for more - * information. - * - *

Common Error Codes

- *
- *
auth/app-deleted
- *
Thrown if the instance of FirebaseApp has been deleted.
- *
auth/app-not-authorized
- *
Thrown if the app identified by the domain where it's hosted, is not - * authorized to use Firebase Authentication with the provided API key. - * Review your key configuration in the Google API console.
- *
auth/argument-error
- *
Thrown if a method is called with incorrect arguments.
- *
auth/invalid-api-key
- *
Thrown if the provided API key is invalid. Please check that you have - * copied it correctly from the Firebase Console.
- *
auth/invalid-user-token
- *
Thrown if the user's credential is no longer valid. The user must sign in - * again.
- *
auth/invalid-tenant-id
- *
Thrown if the tenant ID provided is invalid.
- *
auth/network-request-failed
- *
Thrown if a network error (such as timeout, interrupted connection or - * unreachable host) has occurred.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User.reauthenticateWithCredential} to - * resolve. This does not apply if the user is anonymous.
- *
auth/too-many-requests
- *
Thrown if requests are blocked from a device due to unusual activity. - * Trying again after some delay would unblock.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
auth/user-disabled
- *
Thrown if the user account has been disabled by an administrator. - * Accounts can be enabled or disabled in the Firebase Console, the Auth - * section and Users subsection.
- *
auth/user-token-expired
- *
Thrown if the user's credential has expired. This could also be thrown if - * a user has been deleted. Prompting the user to sign in again should - * resolve this for either case.
- *
auth/web-storage-unsupported
- *
Thrown if the browser does not support web storage or if the user - * disables them.
- *
- */ - interface Error { - name: string; - /** - * Unique error code. - */ - code: string; - /** - * Complete error message. - */ - message: string; - } - - /** - * The account conflict error. - * Refer to {@link firebase.auth.Auth.signInWithPopup} for more information. - * - *

Common Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail} with the error.email - * and then asking the user to sign in using one of the returned providers. - * Once the user is signed in, the original credential retrieved from the - * error.credential can be linked to the user with - * {@link firebase.User.linkWithCredential} to prevent the user from signing - * in again to the original provider via popup or redirect. If you are using - * redirects for sign in, save the credential in session storage and then - * retrieve on redirect and repopulate the credential using for example - * {@link firebase.auth.GoogleAuthProvider.credential} depending on the - * credential provider id and complete the link.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * The fields error.email, error.phoneNumber, and - * error.credential ({@link firebase.auth.AuthCredential}) - * may be provided, depending on the type of credential. You can recover - * from this error by signing in with error.credential directly - * via {@link firebase.auth.Auth.signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User.linkWithCredential} the original credential to that - * newly signed in user.
- *
- */ - interface AuthError extends firebase.auth.Error { - /** - * The {@link firebase.auth.AuthCredential} that can be used to resolve the - * error. - */ - credential?: firebase.auth.AuthCredential; - /** - * The email of the user's account used for sign-in/linking. - */ - email?: string; - /** - * The phone number of the user's account used for sign-in/linking. - */ - phoneNumber?: string; - /** - * The tenant ID being used for sign-in/linking. If you use - * {@link firebase.auth.Auth.signInWithRedirect} to sign in, you have to - * set the tenant ID on Auth instanace again as the tenant ID is not - * persisted after redirection. - */ - tenantId?: string; - } - - /** - * The error thrown when the user needs to provide a second factor to sign in - * successfully. - * The error code for this error is auth/multi-factor-auth-required. - * This error provides a {@link firebase.auth.MultiFactorResolver} object, - * which you can use to get the second sign-in factor from the user. - * - * @example - * ```javascript - * firebase.auth().signInWithEmailAndPassword() - * .then(function(result) { - * // User signed in. No 2nd factor challenge is needed. - * }) - * .catch(function(error) { - * if (error.code == 'auth/multi-factor-auth-required') { - * var resolver = error.resolver; - * var multiFactorHints = resolver.hints; - * } else { - * // Handle other errors. - * } - * }); - * - * resolver.resolveSignIn(multiFactorAssertion) - * .then(function(userCredential) { - * // User signed in. - * }); - * ``` - */ - interface MultiFactorError extends firebase.auth.AuthError { - /** - * The multi-factor resolver to complete second factor sign-in. - */ - resolver: firebase.auth.MultiFactorResolver; - } - - /** - * Facebook auth provider. - * - * @example - * ```javascript - * // Sign in using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // This gives you a Google Access Token. - * var token = result.credential.accessToken; - * } - * var user = result.user; - * }) - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.FacebookAuthProvider(); - * provider.addScope('user_birthday'); - * firebase.auth().signInWithRedirect(provider); - * ``` - * - * @example - * ```javascript - * // Sign in using a popup. - * var provider = new firebase.auth.FacebookAuthProvider(); - * provider.addScope('user_birthday'); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // This gives you a Facebook Access Token. - * var token = result.credential.accessToken; - * // The signed-in user info. - * var user = result.user; - * }); - * ``` - * - * @see {@link firebase.auth.Auth.onAuthStateChanged} to receive sign in state - * changes. - */ - class FacebookAuthProvider extends FacebookAuthProvider_Instance { - static PROVIDER_ID: string; - /** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}. - */ - static FACEBOOK_SIGN_IN_METHOD: string; - /** - * @example - * ```javascript - * var cred = firebase.auth.FacebookAuthProvider.credential( - * // `event` from the Facebook auth.authResponseChange callback. - * event.authResponse.accessToken - * ); - * ``` - * - * @param token Facebook access token. - */ - static credential(token: string): firebase.auth.OAuthCredential; - } - /** - * @hidden - */ - class FacebookAuthProvider_Instance implements firebase.auth.AuthProvider { - /** - * @param scope Facebook OAuth scope. - * @return The provider instance itself. - */ - addScope(scope: string): firebase.auth.AuthProvider; - providerId: string; - /** - * Sets the OAuth custom parameters to pass in a Facebook OAuth request for - * popup and redirect sign-in operations. - * Valid parameters include 'auth_type', 'display' and 'locale'. - * For a detailed list, check the - * {@link https://goo.gl/pve4fo Facebook} - * documentation. - * Reserved required OAuth 2.0 parameters such as 'client_id', 'redirect_uri', - * 'scope', 'response_type' and 'state' are not allowed and will be ignored. - * @param customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return The provider instance itself. - */ - setCustomParameters( - customOAuthParameters: Object - ): firebase.auth.AuthProvider; - } - - /** - * GitHub auth provider. - * - * GitHub requires an OAuth 2.0 redirect, so you can either handle the redirect - * directly, or use the signInWithPopup handler: - * - * @example - * ```javascript - * // Using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // This gives you a GitHub Access Token. - * var token = result.credential.accessToken; - * } - * var user = result.user; - * }).catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * // The email of the user's account used. - * var email = error.email; - * // The firebase.auth.AuthCredential type that was used. - * var credential = error.credential; - * if (errorCode === 'auth/account-exists-with-different-credential') { - * alert('You have signed up with a different provider for that email.'); - * // Handle linking here if your app allows it. - * } else { - * console.error(error); - * } - * }); - * - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.GithubAuthProvider(); - * provider.addScope('repo'); - * firebase.auth().signInWithRedirect(provider); - * ``` - * - * @example - * ```javascript - * // With popup. - * var provider = new firebase.auth.GithubAuthProvider(); - * provider.addScope('repo'); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // This gives you a GitHub Access Token. - * var token = result.credential.accessToken; - * // The signed-in user info. - * var user = result.user; - * }).catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * // The email of the user's account used. - * var email = error.email; - * // The firebase.auth.AuthCredential type that was used. - * var credential = error.credential; - * if (errorCode === 'auth/account-exists-with-different-credential') { - * alert('You have signed up with a different provider for that email.'); - * // Handle linking here if your app allows it. - * } else { - * console.error(error); - * } - * }); - * ``` - * - * @see {@link firebase.auth.Auth.onAuthStateChanged} to receive sign in state - * changes. - */ - class GithubAuthProvider extends GithubAuthProvider_Instance { - static PROVIDER_ID: string; - /** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}. - */ - static GITHUB_SIGN_IN_METHOD: string; - /** - * @example - * ```javascript - * var cred = firebase.auth.FacebookAuthProvider.credential( - * // `event` from the Facebook auth.authResponseChange callback. - * event.authResponse.accessToken - * ); - * ``` - * - * @param token Github access token. - * @return {!firebase.auth.OAuthCredential} The auth provider credential. - */ - static credential(token: string): firebase.auth.OAuthCredential; - } - /** - * @hidden - */ - class GithubAuthProvider_Instance implements firebase.auth.AuthProvider { - /** - * @param scope Github OAuth scope. - * @return The provider instance itself. - */ - addScope(scope: string): firebase.auth.AuthProvider; - providerId: string; - /** - * Sets the OAuth custom parameters to pass in a GitHub OAuth request for popup - * and redirect sign-in operations. - * Valid parameters include 'allow_signup'. - * For a detailed list, check the - * {@link https://developer.github.com/v3/oauth/ GitHub} documentation. - * Reserved required OAuth 2.0 parameters such as 'client_id', 'redirect_uri', - * 'scope', 'response_type' and 'state' are not allowed and will be ignored. - * @param customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return The provider instance itself. - */ - setCustomParameters( - customOAuthParameters: Object - ): firebase.auth.AuthProvider; - } - - /** - * Google auth provider. - * - * @example - * ```javascript - * // Using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // This gives you a Google Access Token. - * var token = result.credential.accessToken; - * } - * var user = result.user; - * }); - * - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.GoogleAuthProvider(); - * provider.addScope('profile'); - * provider.addScope('email'); - * firebase.auth().signInWithRedirect(provider); - * ``` - * - * @example - * ```javascript - * // Using a popup. - * var provider = new firebase.auth.GoogleAuthProvider(); - * provider.addScope('profile'); - * provider.addScope('email'); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // This gives you a Google Access Token. - * var token = result.credential.accessToken; - * // The signed-in user info. - * var user = result.user; - * }); - * ``` - * - * @see {@link firebase.auth.Auth.onAuthStateChanged} to receive sign in state - * changes. - */ - class GoogleAuthProvider extends GoogleAuthProvider_Instance { - static PROVIDER_ID: string; - /** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}. - */ - static GOOGLE_SIGN_IN_METHOD: string; - /** - * Creates a credential for Google. At least one of ID token and access token - * is required. - * - * @example - * ```javascript - * // \`googleUser\` from the onsuccess Google Sign In callback. - * var credential = firebase.auth.GoogleAuthProvider.credential( - googleUser.getAuthResponse().id_token); - * firebase.auth().signInWithCredential(credential) - * ``` - * @param idToken Google ID token. - * @param accessToken Google access token. - * @return The auth provider credential. - */ - static credential( - idToken?: string | null, - accessToken?: string | null - ): firebase.auth.OAuthCredential; - } - /** - * @hidden - */ - class GoogleAuthProvider_Instance implements firebase.auth.AuthProvider { - /** - * @param scope Google OAuth scope. - * @return The provider instance itself. - */ - addScope(scope: string): firebase.auth.AuthProvider; - providerId: string; - /** - * Sets the OAuth custom parameters to pass in a Google OAuth request for popup - * and redirect sign-in operations. - * Valid parameters include 'hd', 'hl', 'include_granted_scopes', 'login_hint' - * and 'prompt'. - * For a detailed list, check the - * {@link https://goo.gl/Xo01Jm Google} - * documentation. - * Reserved required OAuth 2.0 parameters such as 'client_id', 'redirect_uri', - * 'scope', 'response_type' and 'state' are not allowed and will be ignored. - * @param customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return The provider instance itself. - */ - setCustomParameters( - customOAuthParameters: Object - ): firebase.auth.AuthProvider; - } - - /** - * Generic OAuth provider. - * - * @example - * ```javascript - * // Using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // This gives you the OAuth Access Token for that provider. - * var token = result.credential.accessToken; - * } - * var user = result.user; - * }); - * - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.OAuthProvider('google.com'); - * provider.addScope('profile'); - * provider.addScope('email'); - * firebase.auth().signInWithRedirect(provider); - * ``` - * @example - * ```javascript - * // Using a popup. - * var provider = new firebase.auth.OAuthProvider('google.com'); - * provider.addScope('profile'); - * provider.addScope('email'); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // This gives you the OAuth Access Token for that provider. - * var token = result.credential.accessToken; - * // The signed-in user info. - * var user = result.user; - * }); - * ``` - * - * @see {@link firebase.auth.Auth.onAuthStateChanged} to receive sign in state - * changes. - * @param providerId The associated provider ID, such as `github.com`. - */ - class OAuthProvider implements firebase.auth.AuthProvider { - constructor(providerId: string); - providerId: string; - /** - * @param scope Provider OAuth scope to add. - */ - addScope(scope: string): firebase.auth.AuthProvider; - /** - * Creates a Firebase credential from a generic OAuth provider's access token or - * ID token. The raw nonce is required when an ID token with a nonce field is - * provided. The SHA-256 hash of the raw nonce must match the nonce field in - * the ID token. - * - * @example - * ```javascript - * // `googleUser` from the onsuccess Google Sign In callback. - * // Initialize a generate OAuth provider with a `google.com` providerId. - * var provider = new firebase.auth.OAuthProvider('google.com'); - * var credential = provider.credential({ - * idToken: googleUser.getAuthResponse().id_token, - * }); - * firebase.auth().signInWithCredential(credential) - * ``` - * - * @param optionsOrIdToken Either the options object containing - * the ID token, access token and raw nonce or the ID token string. - * @param accessToken The OAuth access token. - */ - credential( - optionsOrIdToken: firebase.auth.OAuthCredentialOptions | string | null, - accessToken?: string - ): firebase.auth.OAuthCredential; - /** - * Sets the OAuth custom parameters to pass in an OAuth request for popup - * and redirect sign-in operations. - * For a detailed list, check the - * reserved required OAuth 2.0 parameters such as `client_id`, `redirect_uri`, - * `scope`, `response_type` and `state` are not allowed and will be ignored. - * @param customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - */ - setCustomParameters( - customOAuthParameters: Object - ): firebase.auth.AuthProvider; - } - - class SAMLAuthProvider implements firebase.auth.AuthProvider { - constructor(providerId: string); - providerId: string; - } - - /** - * Interface representing ID token result obtained from - * {@link firebase.User.getIdTokenResult}. It contains the ID token JWT string - * and other helper properties for getting different data associated with the - * token as well as all the decoded payload claims. - * - * Note that these claims are not to be trusted as they are parsed client side. - * Only server side verification can guarantee the integrity of the token - * claims. - */ - interface IdTokenResult { - /** - * The Firebase Auth ID token JWT string. - */ - token: string; - /** - * The ID token expiration time formatted as a UTC string. - */ - expirationTime: string; - /** - * The authentication time formatted as a UTC string. This is the time the - * user authenticated (signed in) and not the time the token was refreshed. - */ - authTime: string; - /** - * The ID token issued at time formatted as a UTC string. - */ - issuedAtTime: string; - /** - * The sign-in provider through which the ID token was obtained (anonymous, - * custom, phone, password, etc). Note, this does not map to provider IDs. - */ - signInProvider: string | null; - /** - * The type of second factor associated with this session, provided the user - * was multi-factor authenticated (eg. phone, etc). - */ - signInSecondFactor: string | null; - /** - * The entire payload claims of the ID token including the standard reserved - * claims as well as the custom claims. - */ - claims: { - [key: string]: any; - }; - } - - /** - * Defines the options for initializing an - * {@link firebase.auth.OAuthCredential}. For ID tokens with nonce claim, - * the raw nonce has to also be provided. - */ - interface OAuthCredentialOptions { - /** - * The OAuth ID token used to initialize the OAuthCredential. - */ - idToken?: string; - /** - * The OAuth access token used to initialize the OAuthCredential. - */ - accessToken?: string; - /** - * The raw nonce associated with the ID token. It is required when an ID token - * with a nonce field is provided. The SHA-256 hash of the raw nonce must match - * the nonce field in the ID token. - */ - rawNonce?: string; - } - - /** - * The base class for asserting ownership of a second factor. This is used to - * facilitate enrollment of a second factor on an existing user - * or sign-in of a user who already verified the first factor. - * - */ - abstract class MultiFactorAssertion { - /** - * The identifier of the second factor. - */ - factorId: string; - } - - /** - * The class for asserting ownership of a phone second factor. - */ - class PhoneMultiFactorAssertion extends firebase.auth.MultiFactorAssertion { - private constructor(); - } - - /** - * The class used to initialize {@link firebase.auth.PhoneMultiFactorAssertion}. - */ - class PhoneMultiFactorGenerator { - private constructor(); - /** - * The identifier of the phone second factor: `phone`. - */ - static FACTOR_ID: string; - /** - * Initializes the {@link firebase.auth.PhoneMultiFactorAssertion} to confirm ownership - * of the phone second factor. - */ - static assertion( - phoneAuthCredential: firebase.auth.PhoneAuthCredential - ): firebase.auth.PhoneMultiFactorAssertion; - } - - /** - * A structure containing the information of a second factor entity. - */ - interface MultiFactorInfo { - /** - * The multi-factor enrollment ID. - */ - uid: string; - /** - * The user friendly name of the current second factor. - */ - displayName?: string | null; - /** - * The enrollment date of the second factor formatted as a UTC string. - */ - enrollmentTime: string; - /** - * The identifier of the second factor. - */ - factorId: string; - } - - /** - * The subclass of the MultiFactorInfo interface for phone number second factors. - * The factorId of this second factor is - * {@link firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID}. - */ - interface PhoneMultiFactorInfo extends firebase.auth.MultiFactorInfo { - /** - * The phone number associated with the current second factor. - */ - phoneNumber: string; - } - - /** - * The information required to verify the ownership of a phone number. The - * information that's required depends on whether you are doing single-factor - * sign-in, multi-factor enrollment or multi-factor sign-in. - */ - type PhoneInfoOptions = - | firebase.auth.PhoneSingleFactorInfoOptions - | firebase.auth.PhoneMultiFactorEnrollInfoOptions - | firebase.auth.PhoneMultiFactorSignInInfoOptions; - /** - * The phone info options for single-factor sign-in. Only phone number is - * required. - */ - interface PhoneSingleFactorInfoOptions { - phoneNumber: string; - } - - /** - * The phone info options for multi-factor enrollment. Phone number and - * multi-factor session are required. - */ - interface PhoneMultiFactorEnrollInfoOptions { - phoneNumber: string; - session: firebase.auth.MultiFactorSession; - } - - /** - * The phone info options for multi-factor sign-in. Either multi-factor hint or - * multi-factor UID and multi-factor session are required. - */ - interface PhoneMultiFactorSignInInfoOptions { - multiFactorHint?: firebase.auth.MultiFactorInfo; - multiFactorUid?: string; - session: firebase.auth.MultiFactorSession; - } - - /** - * The class used to facilitate recovery from - * {@link firebase.auth.MultiFactorError} when a user needs to provide a second - * factor to sign in. - * - * @example - * ```javascript - * firebase.auth().signInWithEmailAndPassword() - * .then(function(result) { - * // User signed in. No 2nd factor challenge is needed. - * }) - * .catch(function(error) { - * if (error.code == 'auth/multi-factor-auth-required') { - * var resolver = error.resolver; - * // Show UI to let user select second factor. - * var multiFactorHints = resolver.hints; - * } else { - * // Handle other errors. - * } - * }); - * - * // The enrolled second factors that can be used to complete - * // sign-in are returned in the `MultiFactorResolver.hints` list. - * // UI needs to be presented to allow the user to select a second factor - * // from that list. - * - * var selectedHint = // ; selected from multiFactorHints - * var phoneAuthProvider = new firebase.auth.PhoneAuthProvider(); - * var phoneInfoOptions = { - * multiFactorHint: selectedHint, - * session: resolver.session - * }; - * phoneAuthProvider.verifyPhoneNumber( - * phoneInfoOptions, - * appVerifier - * ).then(function(verificationId) { - * // store verificationID and show UI to let user enter verification code. - * }); - * - * // UI to enter verification code and continue. - * // Continue button click handler - * var phoneAuthCredential = - * firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); - * var multiFactorAssertion = - * firebase.auth.PhoneMultiFactorGenerator.assertion(phoneAuthCredential); - * resolver.resolveSignIn(multiFactorAssertion) - * .then(function(userCredential) { - * // User signed in. - * }); - * ``` - */ - class MultiFactorResolver { - private constructor(); - /** - * The Auth instance used to sign in with the first factor. - */ - auth: firebase.auth.Auth; - /** - * The session identifier for the current sign-in flow, which can be used - * to complete the second factor sign-in. - */ - session: firebase.auth.MultiFactorSession; - /** - * The list of hints for the second factors needed to complete the sign-in - * for the current session. - */ - hints: firebase.auth.MultiFactorInfo[]; - /** - * A helper function to help users complete sign in with a second factor - * using an {@link firebase.auth.MultiFactorAssertion} confirming the user - * successfully completed the second factor challenge. - * - *

Error Codes

- *
- *
auth/invalid-verification-code
- *
Thrown if the verification code is not valid.
- *
auth/missing-verification-code
- *
Thrown if the verification code is missing.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
auth/missing-verification-id
- *
Thrown if the verification ID is missing.
- *
auth/code-expired
- *
Thrown if the verification code has expired.
- *
auth/invalid-multi-factor-session
- *
Thrown if the request does not contain a valid proof of first factor - * successful sign-in.
- *
auth/missing-multi-factor-session
- *
Thrown if The request is missing proof of first factor successful - * sign-in.
- *
- * - * @param assertion The multi-factor assertion to resolve sign-in with. - * @return The promise that resolves with the user credential object. - */ - resolveSignIn( - assertion: firebase.auth.MultiFactorAssertion - ): Promise; - } - - /** - * The multi-factor session object used for enrolling a second factor on a - * user or helping sign in an enrolled user with a second factor. - */ - class MultiFactorSession { - private constructor(); - } - - /** - * Classes that represents the Phone Auth credentials returned by a - * {@link firebase.auth.PhoneAuthProvider}. - * - */ - class PhoneAuthCredential extends AuthCredential { - private constructor(); - } - - /** - * Phone number auth provider. - * - * @example - * ```javascript - * // 'recaptcha-container' is the ID of an element in the DOM. - * var applicationVerifier = new firebase.auth.RecaptchaVerifier( - * 'recaptcha-container'); - * var provider = new firebase.auth.PhoneAuthProvider(); - * provider.verifyPhoneNumber('+16505550101', applicationVerifier) - * .then(function(verificationId) { - * var verificationCode = window.prompt('Please enter the verification ' + - * 'code that was sent to your mobile device.'); - * return firebase.auth.PhoneAuthProvider.credential(verificationId, - * verificationCode); - * }) - * .then(function(phoneCredential) { - * return firebase.auth().signInWithCredential(phoneCredential); - * }); - * ``` - * @param auth The Firebase Auth instance in which - * sign-ins should occur. Uses the default Auth instance if unspecified. - */ - class PhoneAuthProvider extends PhoneAuthProvider_Instance { - static PROVIDER_ID: string; - /** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}. - */ - static PHONE_SIGN_IN_METHOD: string; - /** - * Creates a phone auth credential, given the verification ID from - * {@link firebase.auth.PhoneAuthProvider.verifyPhoneNumber} and the code - * that was sent to the user's mobile device. - * - *

Error Codes

- *
- *
auth/missing-verification-code
- *
Thrown if the verification code is missing.
- *
auth/missing-verification-id
- *
Thrown if the verification ID is missing.
- *
- * - * @param verificationId The verification ID returned from - * {@link firebase.auth.PhoneAuthProvider.verifyPhoneNumber}. - * @param verificationCode The verification code sent to the user's - * mobile device. - * @return The auth provider credential. - */ - static credential( - verificationId: string, - verificationCode: string - ): firebase.auth.AuthCredential; - } - /** - * @hidden - */ - class PhoneAuthProvider_Instance implements firebase.auth.AuthProvider { - constructor(auth?: firebase.auth.Auth | null); - providerId: string; - /** - * Starts a phone number authentication flow by sending a verification code to - * the given phone number. Returns an ID that can be passed to - * {@link firebase.auth.PhoneAuthProvider.credential} to identify this flow. - * - * For abuse prevention, this method also requires a - * {@link firebase.auth.ApplicationVerifier}. The Firebase Auth SDK includes - * a reCAPTCHA-based implementation, {@link firebase.auth.RecaptchaVerifier}. - * - *

Error Codes

- *
- *
auth/captcha-check-failed
- *
Thrown if the reCAPTCHA response token was invalid, expired, or if - * this method was called from a non-whitelisted domain.
- *
auth/invalid-phone-number
- *
Thrown if the phone number has an invalid format.
- *
auth/missing-phone-number
- *
Thrown if the phone number is missing.
- *
auth/quota-exceeded
- *
Thrown if the SMS quota for the Firebase project has been exceeded.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given phone number has been - * disabled.
- *
auth/maximum-second-factor-count-exceeded
- *
Thrown if The maximum allowed number of second factors on a user - * has been exceeded.
- *
auth/second-factor-already-in-use
- *
Thrown if the second factor is already enrolled on this account.
- *
auth/unsupported-first-factor
- *
Thrown if the first factor being used to sign in is not supported.
- *
auth/unverified-email
- *
Thrown if the email of the account is not verified.
- *
- * - * @param phoneInfoOptions The user's {@link firebase.auth.PhoneInfoOptions}. - * The phone number should be in E.164 format (e.g. +16505550101). - * @param applicationVerifier - * @return A Promise for the verification ID. - */ - verifyPhoneNumber( - phoneInfoOptions: firebase.auth.PhoneInfoOptions | string, - applicationVerifier: firebase.auth.ApplicationVerifier - ): Promise; - } - - /** - * An {@link https://www.google.com/recaptcha/ reCAPTCHA}-based application - * verifier. - * - * @webonly - * - * @param container The reCAPTCHA container parameter. This - * has different meaning depending on whether the reCAPTCHA is hidden or - * visible. For a visible reCAPTCHA the container must be empty. If a string - * is used, it has to correspond to an element ID. The corresponding element - * must also must be in the DOM at the time of initialization. - * @param parameters The optional reCAPTCHA parameters. Check the - * reCAPTCHA docs for a comprehensive list. All parameters are accepted - * except for the sitekey. Firebase Auth backend provisions a reCAPTCHA for - * each project and will configure this upon rendering. For an invisible - * reCAPTCHA, a size key must have the value 'invisible'. - * @param app The corresponding Firebase app. If none is - * provided, the default Firebase App instance is used. A Firebase App - * instance must be initialized with an API key, otherwise an error will be - * thrown. - */ - class RecaptchaVerifier extends RecaptchaVerifier_Instance {} - /** - * @webonly - * @hidden - */ - class RecaptchaVerifier_Instance - implements firebase.auth.ApplicationVerifier - { - constructor( - container: any | string, - parameters?: Object | null, - app?: firebase.app.App | null - ); - /** - * Clears the reCAPTCHA widget from the page and destroys the current instance. - */ - clear(): void; - /** - * Renders the reCAPTCHA widget on the page. - * @return A Promise that resolves with the - * reCAPTCHA widget ID. - */ - render(): Promise; - /** - * The application verifier type. For a reCAPTCHA verifier, this is 'recaptcha'. - */ - type: string; - /** - * Waits for the user to solve the reCAPTCHA and resolves with the reCAPTCHA - * token. - * @return A Promise for the reCAPTCHA token. - */ - verify(): Promise; - } - - /** - * Twitter auth provider. - * - * @example - * ```javascript - * // Using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // For accessing the Twitter API. - * var token = result.credential.accessToken; - * var secret = result.credential.secret; - * } - * var user = result.user; - * }); - * - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.TwitterAuthProvider(); - * firebase.auth().signInWithRedirect(provider); - * ``` - * @example - * ```javascript - * // Using a popup. - * var provider = new firebase.auth.TwitterAuthProvider(); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // For accessing the Twitter API. - * var token = result.credential.accessToken; - * var secret = result.credential.secret; - * // The signed-in user info. - * var user = result.user; - * }); - * ``` - * - * @see {@link firebase.auth.Auth.onAuthStateChanged} to receive sign in state - * changes. - */ - class TwitterAuthProvider extends TwitterAuthProvider_Instance { - static PROVIDER_ID: string; - /** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}. - * - */ - static TWITTER_SIGN_IN_METHOD: string; - /** - * @param token Twitter access token. - * @param secret Twitter secret. - * @return The auth provider credential. - */ - static credential( - token: string, - secret: string - ): firebase.auth.OAuthCredential; - } - /** - * @hidden - */ - class TwitterAuthProvider_Instance implements firebase.auth.AuthProvider { - providerId: string; - /** - * Sets the OAuth custom parameters to pass in a Twitter OAuth request for popup - * and redirect sign-in operations. - * Valid parameters include 'lang'. - * Reserved required OAuth 1.0 parameters such as 'oauth_consumer_key', - * 'oauth_token', 'oauth_signature', etc are not allowed and will be ignored. - * @param customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return The provider instance itself. - */ - setCustomParameters( - customOAuthParameters: Object - ): firebase.auth.AuthProvider; - } - - /** - * A structure containing a User, an AuthCredential, the operationType, and - * any additional user information that was returned from the identity provider. - * operationType could be 'signIn' for a sign-in operation, 'link' for a linking - * operation and 'reauthenticate' for a reauthentication operation. - */ - type UserCredential = { - additionalUserInfo?: firebase.auth.AdditionalUserInfo | null; - credential: firebase.auth.AuthCredential | null; - operationType?: string | null; - user: firebase.User | null; - }; - - /** - * Interface representing a user's metadata. - */ - interface UserMetadata { - creationTime?: string; - lastSignInTime?: string; - } -} - -/** - * @webonly - */ -declare namespace firebase.analytics { - /** - * The Firebase Analytics service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.analytics `firebase.analytics()`}. - */ - export interface Analytics { - /** - * The {@link firebase.app.App app} associated with the `Analytics` service - * instance. - * - * @example - * ```javascript - * var app = analytics.app; - * ``` - */ - app: firebase.app.App; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'add_payment_info', - eventParams?: { - coupon?: EventParams['coupon']; - currency?: EventParams['currency']; - items?: EventParams['items']; - payment_type?: EventParams['payment_type']; - value?: EventParams['value']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'add_shipping_info', - eventParams?: { - coupon?: EventParams['coupon']; - currency?: EventParams['currency']; - items?: EventParams['items']; - shipping_tier?: EventParams['shipping_tier']; - value?: EventParams['value']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'add_to_cart' | 'add_to_wishlist' | 'remove_from_cart', - eventParams?: { - currency?: EventParams['currency']; - value?: EventParams['value']; - items?: EventParams['items']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'begin_checkout', - eventParams?: { - currency?: EventParams['currency']; - coupon?: EventParams['coupon']; - value?: EventParams['value']; - items?: EventParams['items']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'checkout_progress', - eventParams?: { - currency?: EventParams['currency']; - coupon?: EventParams['coupon']; - value?: EventParams['value']; - items?: EventParams['items']; - checkout_step?: EventParams['checkout_step']; - checkout_option?: EventParams['checkout_option']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * See - * {@link https://developers.google.com/analytics/devguides/collection/ga4/exceptions - * | Measure exceptions}. - */ - logEvent( - eventName: 'exception', - eventParams?: { - description?: EventParams['description']; - fatal?: EventParams['fatal']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'generate_lead', - eventParams?: { - value?: EventParams['value']; - currency?: EventParams['currency']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'login', - eventParams?: { - method?: EventParams['method']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * See - * {@link https://developers.google.com/analytics/devguides/collection/ga4/page-view - * | Page views}. - */ - logEvent( - eventName: 'page_view', - eventParams?: { - page_title?: string; - page_location?: string; - page_path?: string; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'purchase' | 'refund', - eventParams?: { - value?: EventParams['value']; - currency?: EventParams['currency']; - transaction_id: EventParams['transaction_id']; - tax?: EventParams['tax']; - shipping?: EventParams['shipping']; - items?: EventParams['items']; - coupon?: EventParams['coupon']; - affiliation?: EventParams['affiliation']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * See {@link https://firebase.google.com/docs/analytics/screenviews - * | Track Screenviews}. - */ - logEvent( - eventName: 'screen_view', - eventParams?: { - firebase_screen: EventParams['firebase_screen']; - firebase_screen_class: EventParams['firebase_screen_class']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'search' | 'view_search_results', - eventParams?: { - search_term?: EventParams['search_term']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'select_content', - eventParams?: { - content_type?: EventParams['content_type']; - item_id?: EventParams['item_id']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'select_item', - eventParams?: { - items?: EventParams['items']; - item_list_name?: EventParams['item_list_name']; - item_list_id?: EventParams['item_list_id']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'select_promotion' | 'view_promotion', - eventParams?: { - items?: EventParams['items']; - promotion_id?: EventParams['promotion_id']; - promotion_name?: EventParams['promotion_name']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'set_checkout_option', - eventParams?: { - checkout_step?: EventParams['checkout_step']; - checkout_option?: EventParams['checkout_option']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'share', - eventParams?: { - method?: EventParams['method']; - content_type?: EventParams['content_type']; - item_id?: EventParams['item_id']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'sign_up', - eventParams?: { - method?: EventParams['method']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'timing_complete', - eventParams?: { - name: string; - value: number; - event_category?: string; - event_label?: string; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'view_cart' | 'view_item', - eventParams?: { - currency?: EventParams['currency']; - items?: EventParams['items']; - value?: EventParams['value']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: 'view_item_list', - eventParams?: { - items?: EventParams['items']; - item_list_name?: EventParams['item_list_name']; - item_list_id?: EventParams['item_list_id']; - [key: string]: any; - }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sends analytics event with given `eventParams`. This method - * automatically associates this logged event with this Firebase web - * app instance on this device. - * List of recommended event parameters can be found in - * {@link https://developers.google.com/gtagjs/reference/ga4-events - * | the GA4 reference documentation}. - */ - logEvent( - eventName: CustomEventName, - eventParams?: { [key: string]: any }, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Use gtag 'config' command to set 'screen_name'. - */ - setCurrentScreen( - screenName: string, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Use gtag 'config' command to set 'user_id'. - */ - setUserId( - id: string, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Use gtag 'config' command to set all params specified. - */ - setUserProperties( - properties: firebase.analytics.CustomParams, - options?: firebase.analytics.AnalyticsCallOptions - ): void; - - /** - * Sets whether analytics collection is enabled for this app on this device. - * window['ga-disable-analyticsId'] = true; - */ - setAnalyticsCollectionEnabled(enabled: boolean): void; - } - - export type CustomEventName = T extends EventNameString ? never : T; - - /** - * Additional options that can be passed to Firebase Analytics method - * calls such as `logEvent`, `setCurrentScreen`, etc. - */ - export interface AnalyticsCallOptions { - /** - * If true, this config or event call applies globally to all - * analytics properties on the page. - */ - global: boolean; - } - - /** - * Specifies custom options for your Firebase Analytics instance. - * You must set these before initializing `firebase.analytics()`. - */ - export interface SettingsOptions { - /** Sets custom name for `gtag` function. */ - gtagName?: string; - /** Sets custom name for `dataLayer` array used by gtag. */ - dataLayerName?: string; - } - - /** - * Configures Firebase Analytics to use custom `gtag` or `dataLayer` names. - * Intended to be used if `gtag.js` script has been installed on - * this page independently of Firebase Analytics, and is using non-default - * names for either the `gtag` function or for `dataLayer`. - * Must be called before calling `firebase.analytics()` or it won't - * have any effect. - */ - export function settings(settings: firebase.analytics.SettingsOptions): void; - - /** - * Standard gtag.js control parameters. - * For more information, see - * {@link https://developers.google.com/gtagjs/reference/parameter - * the gtag.js documentation on parameters}. - */ - export interface ControlParams { - groups?: string | string[]; - send_to?: string | string[]; - event_callback?: () => void; - event_timeout?: number; - } - - /** - * Standard gtag.js event parameters. - * For more information, see - * {@link https://developers.google.com/gtagjs/reference/parameter - * the gtag.js documentation on parameters}. - */ - export interface EventParams { - checkout_option?: string; - checkout_step?: number; - item_id?: string; - content_type?: string; - coupon?: string; - currency?: string; - description?: string; - fatal?: boolean; - /** - * Firebase-specific. Use to log a `screen_name` to Firebase Analytics. - */ - firebase_screen?: string; - /** - * Firebase-specific. Use to log a `screen_class` to Firebase Analytics. - */ - firebase_screen_class?: string; - items?: Item[]; - method?: string; - number?: string; - promotions?: Promotion[]; - screen_name?: string; - search_term?: string; - shipping?: Currency; - tax?: Currency; - transaction_id?: string; - value?: number; - event_label?: string; - event_category: string; - shipping_tier?: string; - item_list_id?: string; - item_list_name?: string; - promotion_id?: string; - promotion_name?: string; - payment_type?: string; - affiliation?: string; - } - - /** - * Any custom params the user may pass to gtag.js. - */ - export interface CustomParams { - [key: string]: any; - } - - /** - * Type for standard gtag.js event names. `logEvent` also accepts any - * custom string and interprets it as a custom event name. - */ - export type EventNameString = - | 'add_payment_info' - | 'add_shipping_info' - | 'add_to_cart' - | 'add_to_wishlist' - | 'begin_checkout' - | 'checkout_progress' - | 'exception' - | 'generate_lead' - | 'login' - | 'page_view' - | 'purchase' - | 'refund' - | 'remove_from_cart' - | 'screen_view' - | 'search' - | 'select_content' - | 'select_item' - | 'select_promotion' - | 'set_checkout_option' - | 'share' - | 'sign_up' - | 'timing_complete' - | 'view_cart' - | 'view_item' - | 'view_item_list' - | 'view_promotion' - | 'view_search_results'; - - /** - * Enum of standard gtag.js event names provided for convenient - * developer usage. `logEvent` will also accept any custom string - * and interpret it as a custom event name. - */ - export enum EventName { - ADD_PAYMENT_INFO = 'add_payment_info', - ADD_SHIPPING_INFO = 'add_shipping_info', - ADD_TO_CART = 'add_to_cart', - ADD_TO_WISHLIST = 'add_to_wishlist', - BEGIN_CHECKOUT = 'begin_checkout', - /** @deprecated */ - CHECKOUT_PROGRESS = 'checkout_progress', - EXCEPTION = 'exception', - GENERATE_LEAD = 'generate_lead', - LOGIN = 'login', - PAGE_VIEW = 'page_view', - PURCHASE = 'purchase', - REFUND = 'refund', - REMOVE_FROM_CART = 'remove_from_cart', - SCREEN_VIEW = 'screen_view', - SEARCH = 'search', - SELECT_CONTENT = 'select_content', - SELECT_ITEM = 'select_item', - SELECT_PROMOTION = 'select_promotion', - /** @deprecated */ - SET_CHECKOUT_OPTION = 'set_checkout_option', - SHARE = 'share', - SIGN_UP = 'sign_up', - TIMING_COMPLETE = 'timing_complete', - VIEW_CART = 'view_cart', - VIEW_ITEM = 'view_item', - VIEW_ITEM_LIST = 'view_item_list', - VIEW_PROMOTION = 'view_promotion', - VIEW_SEARCH_RESULTS = 'view_search_results' - } - - export type Currency = string | number; - - export interface Item { - item_id?: string; - item_name?: string; - item_brand?: string; - item_category?: string; - item_category2?: string; - item_category3?: string; - item_category4?: string; - item_category5?: string; - item_variant?: string; - price?: Currency; - quantity?: number; - index?: number; - coupon?: string; - item_list_name?: string; - item_list_id?: string; - discount?: Currency; - affiliation?: string; - creative_name?: string; - creative_slot?: string; - promotion_id?: string; - promotion_name?: string; - location_id?: string; - /** @deprecated Use item_brand instead. */ - brand?: string; - /** @deprecated Use item_category instead. */ - category?: string; - /** @deprecated Use item_id instead. */ - id?: string; - /** @deprecated Use item_name instead. */ - name?: string; - } - - /** @deprecated Use Item instead. */ - export interface Promotion { - creative_name?: string; - creative_slot?: string; - id?: string; - name?: string; - } - - /** - * An async function that returns true if current browser context supports initialization of analytics module - * (`firebase.analytics()`). - * - * Returns false otherwise. - * - * - */ - function isSupported(): Promise; -} - -declare namespace firebase.auth.Auth { - type Persistence = string; - /** - * An enumeration of the possible persistence mechanism types. - */ - var Persistence: { - /** - * Indicates that the state will be persisted even when the browser window is - * closed or the activity is destroyed in react-native. - */ - LOCAL: Persistence; - /** - * Indicates that the state will only be stored in memory and will be cleared - * when the window or activity is refreshed. - */ - NONE: Persistence; - /** - * Indicates that the state will only persist in current session/tab, relevant - * to web only, and will be cleared when the tab is closed. - */ - SESSION: Persistence; - }; -} - -declare namespace firebase.User { - /** - * This is the interface that defines the multi-factor related properties and - * operations pertaining to a {@link firebase.User}. - */ - interface MultiFactorUser { - /** - * Returns a list of the user's enrolled second factors. - */ - enrolledFactors: firebase.auth.MultiFactorInfo[]; - /** - * Enrolls a second factor as identified by the - * {@link firebase.auth.MultiFactorAssertion} for the current user. - * On resolution, the user tokens are updated to reflect the change in the - * JWT payload. - * Accepts an additional display name parameter used to identify the second - * factor to the end user. - * Recent re-authentication is required for this operation to succeed. - * On successful enrollment, existing Firebase sessions (refresh tokens) are - * revoked. When a new factor is enrolled, an email notification is sent - * to the user’s email. - * - *

Error Codes

- *
- *
auth/invalid-verification-code
- *
Thrown if the verification code is not valid.
- *
auth/missing-verification-code
- *
Thrown if the verification code is missing.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
auth/missing-verification-id
- *
Thrown if the verification ID is missing.
- *
auth/code-expired
- *
Thrown if the verification code has expired.
- *
auth/maximum-second-factor-count-exceeded
- *
Thrown if The maximum allowed number of second factors on a user - * has been exceeded.
- *
auth/second-factor-already-in-use
- *
Thrown if the second factor is already enrolled on this account.
- *
auth/unsupported-first-factor
- *
Thrown if the first factor being used to sign in is not supported.
- *
auth/unverified-email
- *
Thrown if the email of the account is not verified.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User.reauthenticateWithCredential} to - * resolve.
- *
- * - * @example - * ```javascript - * firebase.auth().currentUser.multiFactor.getSession() - * .then(function(multiFactorSession) { - * // Send verification code - * var phoneAuthProvider = new firebase.auth.PhoneAuthProvider(); - * var phoneInfoOptions = { - * phoneNumber: phoneNumber, - * session: multiFactorSession - * }; - * return phoneAuthProvider.verifyPhoneNumber( - * phoneInfoOptions, appVerifier); - * }).then(function(verificationId) { - * // Store verificationID and show UI to let user enter verification code. - * }); - * - * var phoneAuthCredential = - * firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); - * var multiFactorAssertion = - * firebase.auth.PhoneMultiFactorGenerator.assertion(phoneAuthCredential); - * firebase.auth().currentUser.multiFactor.enroll(multiFactorAssertion) - * .then(function() { - * // Second factor enrolled. - * }); - * ``` - * - * @param assertion The multi-factor assertion to enroll with. - * @param displayName The display name of the second factor. - */ - enroll( - assertion: firebase.auth.MultiFactorAssertion, - displayName?: string | null - ): Promise; - /** - * Returns the session identifier for a second factor enrollment operation. - * This is used to identify the current user trying to enroll a second factor. - * @return The promise that resolves with the - * {@link firebase.auth.MultiFactorSession}. - * - *

Error Codes

- *
- *
auth/user-token-expired
- *
Thrown if the token of the user is expired.
- *
- */ - getSession(): Promise; - /** - * Unenrolls the specified second factor. To specify the factor to remove, pass - * a {@link firebase.auth.MultiFactorInfo} object - * (retrieved from enrolledFactors()) - * or the factor's UID string. - * Sessions are not revoked when the account is downgraded. An email - * notification is likely to be sent to the user notifying them of the change. - * Recent re-authentication is required for this operation to succeed. - * When an existing factor is unenrolled, an email notification is sent to the - * user’s email. - * - *

Error Codes

- *
- *
auth/multi-factor-info-not-found
- *
Thrown if the user does not have a second factor matching the - * identifier provided.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User.reauthenticateWithCredential} to - * resolve.
- *
- * - * @example - * ```javascript - * var options = firebase.auth().currentUser.multiFactor.enrolledFactors; - * // Present user the option to unenroll. - * return firebase.auth().currentUser.multiFactor.unenroll(options[i]) - * .then(function() { - * // User successfully unenrolled selected factor. - * }).catch(function(error) { - * // Handler error. - * }); - * ``` - * - * @param option The multi-factor option to unenroll. - */ - unenroll(option: firebase.auth.MultiFactorInfo | string): Promise; - } -} - -declare namespace firebase.auth.ActionCodeInfo { - type Operation = string; - /** - * An enumeration of the possible email action types. - */ - var Operation: { - /** - * The email link sign-in action. - */ - EMAIL_SIGNIN: Operation; - /** - * The password reset action. - */ - PASSWORD_RESET: Operation; - /** - * The email revocation action. - */ - RECOVER_EMAIL: Operation; - /** - * The revert second factor addition email action. - */ - REVERT_SECOND_FACTOR_ADDITION: Operation; - /** - * The verify and update email action. - */ - VERIFY_AND_CHANGE_EMAIL: Operation; - /** - * The email verification action. - */ - VERIFY_EMAIL: Operation; - }; -} - -declare namespace firebase.database { - /** - * A `DataSnapshot` contains data from a Database location. - * - * Any time you read data from the Database, you receive the data as a - * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach - * with `on()` or `once()`. You can extract the contents of the snapshot as a - * JavaScript object by calling the `val()` method. Alternatively, you can - * traverse into the snapshot by calling `child()` to return child snapshots - * (which you could then call `val()` on). - * - * A `DataSnapshot` is an efficiently generated, immutable copy of the data at - * a Database location. It cannot be modified and will never change (to modify - * data, you always call the `set()` method on a `Reference` directly). - * - */ - interface DataSnapshot { - /** - * Gets another `DataSnapshot` for the location at the specified relative path. - * - * Passing a relative path to the `child()` method of a DataSnapshot returns - * another `DataSnapshot` for the location at the specified relative path. The - * relative path can either be a simple child name (for example, "ada") or a - * deeper, slash-separated path (for example, "ada/name/first"). If the child - * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot` - * whose value is `null`) is returned. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Test for the existence of certain keys within a DataSnapshot - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var name = snapshot.child("name").val(); // {first:"Ada",last:"Lovelace"} - * var firstName = snapshot.child("name/first").val(); // "Ada" - * var lastName = snapshot.child("name").child("last").val(); // "Lovelace" - * var age = snapshot.child("age").val(); // null - * }); - * ``` - * - * @param path A relative path to the location of child data. - */ - child(path: string): firebase.database.DataSnapshot; - /** - * Returns true if this `DataSnapshot` contains any data. It is slightly more - * efficient than using `snapshot.val() !== null`. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Test for the existence of certain keys within a DataSnapshot - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.exists(); // true - * var b = snapshot.child("name").exists(); // true - * var c = snapshot.child("name/first").exists(); // true - * var d = snapshot.child("name/middle").exists(); // false - * }); - * ``` - */ - exists(): boolean; - /** - * Exports the entire contents of the DataSnapshot as a JavaScript object. - * - * The `exportVal()` method is similar to `val()`, except priority information - * is included (if available), making it suitable for backing up your data. - * - * @return The DataSnapshot's contents as a JavaScript value (Object, - * Array, string, number, boolean, or `null`). - */ - exportVal(): any; - /** - * Enumerates the top-level children in the `DataSnapshot`. - * - * Because of the way JavaScript objects work, the ordering of data in the - * JavaScript object returned by `val()` is not guaranteed to match the ordering - * on the server nor the ordering of `child_added` events. That is where - * `forEach()` comes in handy. It guarantees the children of a `DataSnapshot` - * will be iterated in their query order. - * - * If no explicit `orderBy*()` method is used, results are returned - * ordered by key (unless priorities are used, in which case, results are - * returned by priority). - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "users": { - * "ada": { - * "first": "Ada", - * "last": "Lovelace" - * }, - * "alan": { - * "first": "Alan", - * "last": "Turing" - * } - * } - * } - * - * // Loop through users in order with the forEach() method. The callback - * // provided to forEach() will be called synchronously with a DataSnapshot - * // for each child: - * var query = firebase.database().ref("users").orderByKey(); - * query.once("value") - * .then(function(snapshot) { - * snapshot.forEach(function(childSnapshot) { - * // key will be "ada" the first time and "alan" the second time - * var key = childSnapshot.key; - * // childData will be the actual contents of the child - * var childData = childSnapshot.val(); - * }); - * }); - * ``` - * - * @example - * ```javascript - * // You can cancel the enumeration at any point by having your callback - * // function return true. For example, the following code sample will only - * // fire the callback function one time: - * var query = firebase.database().ref("users").orderByKey(); - * query.once("value") - * .then(function(snapshot) { - * snapshot.forEach(function(childSnapshot) { - * var key = childSnapshot.key; // "ada" - * - * // Cancel enumeration - * return true; - * }); - * }); - * ``` - * - * @param action A function - * that will be called for each child DataSnapshot. The callback can return - * true to cancel further enumeration. - * @return true if enumeration was canceled due to your callback - * returning true. - */ - forEach( - action: (a: firebase.database.DataSnapshot) => boolean | void - ): boolean; - /** - * Gets the priority value of the data in this `DataSnapshot`. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - */ - getPriority(): string | number | null; - /** - * Returns true if the specified child path has (non-null) data. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Determine which child keys in DataSnapshot have data. - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var hasName = snapshot.hasChild("name"); // true - * var hasAge = snapshot.hasChild("age"); // false - * }); - * ``` - * - * @param path A relative path to the location of a potential child. - * @return `true` if data exists at the specified child path; else - * `false`. - */ - hasChild(path: string): boolean; - /** - * Returns whether or not the `DataSnapshot` has any non-`null` child - * properties. - * - * You can use `hasChildren()` to determine if a `DataSnapshot` has any - * children. If it does, you can enumerate them using `forEach()`. If it - * doesn't, then either this snapshot contains a primitive value (which can be - * retrieved with `val()`) or it is empty (in which case, `val()` will return - * `null`). - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.hasChildren(); // true - * var b = snapshot.child("name").hasChildren(); // true - * var c = snapshot.child("name/first").hasChildren(); // false - * }); - * ``` - * - * @return true if this snapshot has any children; else false. - */ - hasChildren(): boolean; - /** - * The key (last part of the path) of the location of this `DataSnapshot`. - * - * The last token in a Database location is considered its key. For example, - * "ada" is the key for the /users/ada/ node. Accessing the key on any - * `DataSnapshot` will return the key for the location that generated it. - * However, accessing the key on the root URL of a Database will return `null`. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var key = snapshot.key; // "ada" - * var childKey = snapshot.child("name/last").key; // "last" - * }); - * ``` - * - * @example - * ```javascript - * var rootRef = firebase.database().ref(); - * rootRef.once("value") - * .then(function(snapshot) { - * var key = snapshot.key; // null - * var childKey = snapshot.child("users/ada").key; // "ada" - * }); - * ``` - */ - key: string | null; - /** - * Returns the number of child properties of this `DataSnapshot`. - * - * @example - * ```javascript - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.numChildren(); // 1 ("name") - * var b = snapshot.child("name").numChildren(); // 2 ("first", "last") - * var c = snapshot.child("name/first").numChildren(); // 0 - * }); - * ``` - */ - numChildren(): number; - /** - * Extracts a JavaScript value from a `DataSnapshot`. - * - * Depending on the data in a `DataSnapshot`, the `val()` method may return a - * scalar type (string, number, or boolean), an array, or an object. It may also - * return null, indicating that the `DataSnapshot` is empty (contains no data). - * - * @example - * ```javascript - * // Write and then read back a string from the Database. - * ref.set("hello") - * .then(function() { - * return ref.once("value"); - * }) - * .then(function(snapshot) { - * var data = snapshot.val(); // data === "hello" - * }); - * ``` - * - * @example - * ```javascript - * // Write and then read back a JavaScript object from the Database. - * ref.set({ name: "Ada", age: 36 }) - * .then(function() { - * return ref.once("value"); - * }) - * .then(function(snapshot) { - * var data = snapshot.val(); - * // data is { "name": "Ada", "age": 36 } - * // data.name === "Ada" - * // data.age === 36 - * }); - * ``` - * - * @return The DataSnapshot's contents as a JavaScript value (Object, - * Array, string, number, boolean, or `null`). - */ - val(): any; - /** - * The `Reference` for the location that generated this `DataSnapshot`. - */ - ref: firebase.database.Reference; - /** - * Returns a JSON-serializable representation of this object. - */ - toJSON(): Object | null; - } - - /** - * The Firebase Database service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.database `firebase.database()`}. - * - * See - * {@link - * https://firebase.google.com/docs/database/web/start/ - * Installation & Setup in JavaScript} - * for a full guide on how to use the Firebase Database service. - */ - interface Database { - /** - * The {@link firebase.app.App app} associated with the `Database` service - * instance. - * - * @example - * ```javascript - * var app = database.app; - * ``` - */ - app: firebase.app.App; - /** - * Modify this instance to communicate with the Realtime Database emulator. - * - *

Note: This method must be called before performing any other operation. - * - * @param host the emulator host (ex: localhost) - * @param port the emulator port (ex: 8080) - */ - useEmulator(host: string, port: number): void; - /** - * Disconnects from the server (all Database operations will be completed - * offline). - * - * The client automatically maintains a persistent connection to the Database - * server, which will remain active indefinitely and reconnect when - * disconnected. However, the `goOffline()` and `goOnline()` methods may be used - * to control the client connection in cases where a persistent connection is - * undesirable. - * - * While offline, the client will no longer receive data updates from the - * Database. However, all Database operations performed locally will continue to - * immediately fire events, allowing your application to continue behaving - * normally. Additionally, each operation performed locally will automatically - * be queued and retried upon reconnection to the Database server. - * - * To reconnect to the Database and begin receiving remote events, see - * `goOnline()`. - * - * @example - * ```javascript - * firebase.database().goOffline(); - * ``` - */ - goOffline(): any; - /** - * Reconnects to the server and synchronizes the offline Database state - * with the server state. - * - * This method should be used after disabling the active connection with - * `goOffline()`. Once reconnected, the client will transmit the proper data - * and fire the appropriate events so that your client "catches up" - * automatically. - * - * @example - * ```javascript - * firebase.database().goOnline(); - * ``` - */ - goOnline(): any; - /** - * Returns a `Reference` representing the location in the Database - * corresponding to the provided path. If no path is provided, the `Reference` - * will point to the root of the Database. - * - * @example - * ```javascript - * // Get a reference to the root of the Database - * var rootRef = firebase.database().ref(); - * ``` - * - * @example - * ```javascript - * // Get a reference to the /users/ada node - * var adaRef = firebase.database().ref("users/ada"); - * // The above is shorthand for the following operations: - * //var rootRef = firebase.database().ref(); - * //var adaRef = rootRef.child("users/ada"); - * ``` - * - * @param path Optional path representing the location the returned - * `Reference` will point. If not provided, the returned `Reference` will - * point to the root of the Database. - * @return If a path is provided, a `Reference` - * pointing to the provided path. Otherwise, a `Reference` pointing to the - * root of the Database. - */ - ref(path?: string): firebase.database.Reference; - /** - * Returns a `Reference` representing the location in the Database - * corresponding to the provided Firebase URL. - * - * An exception is thrown if the URL is not a valid Firebase Database URL or it - * has a different domain than the current `Database` instance. - * - * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored - * and are not applied to the returned `Reference`. - * - * @example - * ```javascript - * // Get a reference to the root of the Database - * var rootRef = firebase.database().ref("https://.firebaseio.com"); - * ``` - * - * @example - * ```javascript - * // Get a reference to the /users/ada node - * var adaRef = firebase.database().ref("https://.firebaseio.com/users/ada"); - * ``` - * - * @param url The Firebase URL at which the returned `Reference` will - * point. - * @return A `Reference` pointing to the provided - * Firebase URL. - */ - refFromURL(url: string): firebase.database.Reference; - } - - /** - * The `onDisconnect` class allows you to write or clear data when your client - * disconnects from the Database server. These updates occur whether your - * client disconnects cleanly or not, so you can rely on them to clean up data - * even if a connection is dropped or a client crashes. - * - * The `onDisconnect` class is most commonly used to manage presence in - * applications where it is useful to detect how many clients are connected and - * when other clients disconnect. See - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information. - * - * To avoid problems when a connection is dropped before the requests can be - * transferred to the Database server, these functions should be called before - * writing any data. - * - * Note that `onDisconnect` operations are only triggered once. If you want an - * operation to occur each time a disconnect occurs, you'll need to re-establish - * the `onDisconnect` operations each time you reconnect. - */ - interface OnDisconnect { - /** - * Cancels all previously queued `onDisconnect()` set or update events for this - * location and all children. - * - * If a write has been queued for this location via a `set()` or `update()` at a - * parent location, the write at this location will be canceled, though writes - * to sibling locations will still occur. - * - * @example - * ```javascript - * var ref = firebase.database().ref("onlineState"); - * ref.onDisconnect().set(false); - * // ... sometime later - * ref.onDisconnect().cancel(); - * ``` - * - * @param onComplete An optional callback function that will - * be called when synchronization to the server has completed. The callback - * will be passed a single parameter: null for success, or an Error object - * indicating a failure. - * @return Resolves when synchronization to the server - * is complete. - */ - cancel(onComplete?: (a: Error | null) => any): Promise; - /** - * Ensures the data at this location is deleted when the client is disconnected - * (due to closing the browser, navigating to a new page, or network issues). - * - * @param onComplete An optional callback function that will - * be called when synchronization to the server has completed. The callback - * will be passed a single parameter: null for success, or an Error object - * indicating a failure. - * @return Resolves when synchronization to the server - * is complete. - */ - remove(onComplete?: (a: Error | null) => any): Promise; - /** - * Ensures the data at this location is set to the specified value when the - * client is disconnected (due to closing the browser, navigating to a new page, - * or network issues). - * - * `set()` is especially useful for implementing "presence" systems, where a - * value should be changed or cleared when a user disconnects so that they - * appear "offline" to other users. See - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information. - * - * Note that `onDisconnect` operations are only triggered once. If you want an - * operation to occur each time a disconnect occurs, you'll need to re-establish - * the `onDisconnect` operations each time. - * - * @example - * ```javascript - * var ref = firebase.database().ref("users/ada/status"); - * ref.onDisconnect().set("I disconnected!"); - * ``` - * - * @param value The value to be written to this location on - * disconnect (can be an object, array, string, number, boolean, or null). - * @param onComplete An optional callback function that - * will be called when synchronization to the Database server has completed. - * The callback will be passed a single parameter: null for success, or an - * `Error` object indicating a failure. - * @return Resolves when synchronization to the - * Database is complete. - */ - set(value: any, onComplete?: (a: Error | null) => any): Promise; - /** - * Ensures the data at this location is set to the specified value and priority - * when the client is disconnected (due to closing the browser, navigating to a - * new page, or network issues). - */ - setWithPriority( - value: any, - priority: number | string | null, - onComplete?: (a: Error | null) => any - ): Promise; - /** - * Writes multiple values at this location when the client is disconnected (due - * to closing the browser, navigating to a new page, or network issues). - * - * The `values` argument contains multiple property-value pairs that will be - * written to the Database together. Each child property can either be a simple - * property (for example, "name") or a relative path (for example, "name/first") - * from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update - * only the referenced properties at the current location (instead of replacing - * all the child properties at the current location). - * - * See more examples using the connected version of - * {@link firebase.database.Reference.update `update()`}. - * - * @example - * ```javascript - * var ref = firebase.database().ref("users/ada"); - * ref.update({ - * onlineState: true, - * status: "I'm online." - * }); - * ref.onDisconnect().update({ - * onlineState: false, - * status: "I'm offline." - * }); - * ``` - * - * @param values Object containing multiple values. - * @param onComplete An optional callback function that will - * be called when synchronization to the server has completed. The - * callback will be passed a single parameter: null for success, or an Error - * object indicating a failure. - * @return Resolves when synchronization to the - * Database is complete. - */ - update(values: Object, onComplete?: (a: Error | null) => any): Promise; - } - - type EventType = - | 'value' - | 'child_added' - | 'child_changed' - | 'child_moved' - | 'child_removed'; - - /** - * A `Query` sorts and filters the data at a Database location so only a subset - * of the child data is included. This can be used to order a collection of - * data by some attribute (for example, height of dinosaurs) as well as to - * restrict a large list of items (for example, chat messages) down to a number - * suitable for synchronizing to the client. Queries are created by chaining - * together one or more of the filter methods defined here. - * - * Just as with a `Reference`, you can receive data from a `Query` by using the - * `on()` method. You will only receive events and `DataSnapshot`s for the - * subset of the data that matches your query. - * - * Read our documentation on - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data} for more information. - */ - interface Query { - /** - * Creates a `Query` with the specified ending point. - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` - * allows you to choose arbitrary starting and ending points for your queries. - * - * The ending point is inclusive, so children with exactly the specified value - * will be included in the query. The optional key argument can be used to - * further limit the range of the query. If it is specified, then children that - * have exactly the specified value must also have a key name less than or equal - * to the specified key. - * - * You can read more about `endAt()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find all dinosaurs whose names come before Pterodactyl lexicographically. - * // Include Pterodactyl in the result. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByKey().endAt("pterodactyl").on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * ``` - * - * @param value The value to end at. The argument - * type depends on which `orderBy*()` function was used in this query. - * Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param key The child key to end at, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * child, value, or priority. - */ - endAt( - value: number | string | boolean | null, - key?: string - ): firebase.database.Query; - /** - * Creates a `Query` with the specified ending point (exclusive). - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` - * allows you to choose arbitrary starting and ending points for your queries. - * - * The ending point is exclusive. If only a value is provided, children - * with a value less than the specified value will be included in the query. - * If a key is specified, then children must have a value lesss than or equal - * to the specified value and a a key name less than the specified key. - * - * @example - * ```javascript - * // Find all dinosaurs whose names come before Pterodactyl lexicographically. - * // Do not include Pterodactyl in the result. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByKey().endBefore("pterodactyl").on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * - * @param value The value to end before. The argument - * type depends on which `orderBy*()` function was used in this query. - * Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param key The child key to end before, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * child, value, or priority. - */ - endBefore( - value: number | string | boolean | null, - key?: string - ): firebase.database.Query; - /** - * Creates a `Query` that includes children that match the specified value. - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` - * allows you to choose arbitrary starting and ending points for your queries. - * - * The optional key argument can be used to further limit the range of the - * query. If it is specified, then children that have exactly the specified - * value must also have exactly the specified key as their key name. This can be - * used to filter result sets with many matches for the same value. - * - * You can read more about `equalTo()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find all dinosaurs whose height is exactly 25 meters. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").equalTo(25).on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * ``` - * - * @param value The value to match for. The - * argument type depends on which `orderBy*()` function was used in this - * query. Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param key The child key to start at, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * child, value, or priority. - */ - equalTo( - value: number | string | boolean | null, - key?: string - ): firebase.database.Query; - /** - * Returns whether or not the current and provided queries represent the same - * location, have the same query parameters, and are from the same instance of - * `firebase.app.App`. - * - * Two `Reference` objects are equivalent if they represent the same location - * and are from the same instance of `firebase.app.App`. - * - * Two `Query` objects are equivalent if they represent the same location, have - * the same query parameters, and are from the same instance of - * `firebase.app.App`. Equivalent queries share the same sort order, limits, and - * starting and ending points. - * - * @example - * ```javascript - * var rootRef = firebase.database.ref(); - * var usersRef = rootRef.child("users"); - * - * usersRef.isEqual(rootRef); // false - * usersRef.isEqual(rootRef.child("users")); // true - * usersRef.parent.isEqual(rootRef); // true - * ``` - * - * @example - * ```javascript - * var rootRef = firebase.database.ref(); - * var usersRef = rootRef.child("users"); - * var usersQuery = usersRef.limitToLast(10); - * - * usersQuery.isEqual(usersRef); // false - * usersQuery.isEqual(usersRef.limitToLast(10)); // true - * usersQuery.isEqual(rootRef.limitToLast(10)); // false - * usersQuery.isEqual(usersRef.orderByKey().limitToLast(10)); // false - * ``` - * - * @param other The query to compare against. - * @return Whether or not the current and provided queries are - * equivalent. - */ - isEqual(other: firebase.database.Query | null): boolean; - /** - * Generates a new `Query` limited to the first specific number of children. - * - * The `limitToFirst()` method is used to set a maximum number of children to be - * synced for a given callback. If we set a limit of 100, we will initially only - * receive up to 100 `child_added` events. If we have fewer than 100 messages - * stored in our Database, a `child_added` event will fire for each message. - * However, if we have over 100 messages, we will only receive a `child_added` - * event for the first 100 ordered messages. As items change, we will receive - * `child_removed` events for each item that drops out of the active list so - * that the total number stays at 100. - * - * You can read more about `limitToFirst()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find the two shortest dinosaurs. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").limitToFirst(2).on("child_added", function(snapshot) { - * // This will be called exactly two times (unless there are less than two - * // dinosaurs in the Database). - * - * // It will also get fired again if one of the first two dinosaurs is - * // removed from the data set, as a new dinosaur will now be the second - * // shortest. - * console.log(snapshot.key); - * }); - * ``` - * - * @param limit The maximum number of nodes to include in this query. - */ - limitToFirst(limit: number): firebase.database.Query; - /** - * Generates a new `Query` object limited to the last specific number of - * children. - * - * The `limitToLast()` method is used to set a maximum number of children to be - * synced for a given callback. If we set a limit of 100, we will initially only - * receive up to 100 `child_added` events. If we have fewer than 100 messages - * stored in our Database, a `child_added` event will fire for each message. - * However, if we have over 100 messages, we will only receive a `child_added` - * event for the last 100 ordered messages. As items change, we will receive - * `child_removed` events for each item that drops out of the active list so - * that the total number stays at 100. - * - * You can read more about `limitToLast()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find the two heaviest dinosaurs. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("weight").limitToLast(2).on("child_added", function(snapshot) { - * // This callback will be triggered exactly two times, unless there are - * // fewer than two dinosaurs stored in the Database. It will also get fired - * // for every new, heavier dinosaur that gets added to the data set. - * console.log(snapshot.key); - * }); - * ``` - * - * @param limit The maximum number of nodes to include in this query. - */ - limitToLast(limit: number): firebase.database.Query; - /** - * Detaches a callback previously attached with `on()`. - * - * Detach a callback previously attached with `on()`. Note that if `on()` was - * called multiple times with the same eventType and callback, the callback - * will be called multiple times for each event, and `off()` must be called - * multiple times to remove the callback. Calling `off()` on a parent listener - * will not automatically remove listeners registered on child nodes, `off()` - * must also be called on any child listeners to remove the callback. - * - * If a callback is not specified, all callbacks for the specified eventType - * will be removed. Similarly, if no eventType is specified, all callbacks - * for the `Reference` will be removed. - * - * @example - * ```javascript - * var onValueChange = function(dataSnapshot) { ... }; - * ref.on('value', onValueChange); - * ref.child('meta-data').on('child_added', onChildAdded); - * // Sometime later... - * ref.off('value', onValueChange); - * - * // You must also call off() for any child listeners on ref - * // to cancel those callbacks - * ref.child('meta-data').off('child_added', onValueAdded); - * ``` - * - * @example - * ```javascript - * // Or you can save a line of code by using an inline function - * // and on()'s return value. - * var onValueChange = ref.on('value', function(dataSnapshot) { ... }); - * // Sometime later... - * ref.off('value', onValueChange); - * ``` - * - * @param eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." If - * omitted, all callbacks for the `Reference` will be removed. - * @param callback The callback function that was passed to `on()` or - * `undefined` to remove all callbacks. - * @param context The context that was passed to `on()`. - */ - off( - eventType?: EventType, - callback?: (a: firebase.database.DataSnapshot, b?: string | null) => any, - context?: Object | null - ): void; - - /** - * Gets the most up-to-date result for this query. - * - * @return A promise which resolves to the resulting DataSnapshot if - * a value is available, or rejects if the client is unable to return - * a value (e.g., if the server is unreachable and there is nothing - * cached). - */ - get(): Promise; - - /** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Use `off( )` to stop receiving updates. See - * {@link https://firebase.google.com/docs/database/web/retrieve-data - * Retrieve Data on the Web} - * for more details. - * - *

value event

- * - * This event will trigger once with the initial data stored at this location, - * and then trigger again each time the data changes. The `DataSnapshot` passed - * to the callback will be for the location at which `on()` was called. It - * won't trigger until the entire contents has been synchronized. If the - * location has no data, it will be triggered with an empty `DataSnapshot` - * (`val()` will return `null`). - * - *

child_added event

- * - * This event will be triggered once for each initial child at this location, - * and it will be triggered again every time a new child is added. The - * `DataSnapshot` passed into the callback will reflect the data for the - * relevant child. For ordering purposes, it is passed a second argument which - * is a string containing the key of the previous sibling child by sort order, - * or `null` if it is the first child. - * - *

child_removed event

- * - * This event will be triggered once every time a child is removed. The - * `DataSnapshot` passed into the callback will be the old data for the child - * that was removed. A child will get removed when either: - * - * - a client explicitly calls `remove()` on that child or one of its ancestors - * - a client calls `set(null)` on that child or one of its ancestors - * - that child has all of its children removed - * - there is a query in effect which now filters out the child (because it's - * sort order changed or the max limit was hit) - * - *

child_changed event

- * - * This event will be triggered when the data stored in a child (or any of its - * descendants) changes. Note that a single `child_changed` event may represent - * multiple changes to the child. The `DataSnapshot` passed to the callback will - * contain the new child contents. For ordering purposes, the callback is also - * passed a second argument which is a string containing the key of the previous - * sibling child by sort order, or `null` if it is the first child. - * - *

child_moved event

- * - * This event will be triggered when a child's sort order changes such that its - * position relative to its siblings changes. The `DataSnapshot` passed to the - * callback will be for the data of the child that has moved. It is also passed - * a second argument which is a string containing the key of the previous - * sibling child by sort order, or `null` if it is the first child. - * - * @example **Handle a new value:** - * ```javascript - * ref.on('value', function(dataSnapshot) { - * ... - * }); - * ``` - * - * @example **Handle a new child:** - * ```javascript - * ref.on('child_added', function(childSnapshot, prevChildKey) { - * ... - * }); - * ``` - * - * @example **Handle child removal:** - * ```javascript - * ref.on('child_removed', function(oldChildSnapshot) { - * ... - * }); - * ``` - * - * @example **Handle child data changes:** - * ```javascript - * ref.on('child_changed', function(childSnapshot, prevChildKey) { - * ... - * }); - * ``` - * - * @example **Handle child ordering changes:** - * ```javascript - * ref.on('child_moved', function(childSnapshot, prevChildKey) { - * ... - * }); - * ``` - * - * @param eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." - * @param callback A - * callback that fires when the specified event occurs. The callback will be - * passed a DataSnapshot. For ordering purposes, "child_added", - * "child_changed", and "child_moved" will also be passed a string containing - * the key of the previous child, by sort order, or `null` if it is the - * first child. - * @param cancelCallbackOrContext An optional - * callback that will be notified if your event subscription is ever canceled - * because your client does not have permission to read this data (or it had - * permission but has now lost it). This callback will be passed an `Error` - * object indicating why the failure occurred. - * @param context If provided, this object will be used as `this` - * when calling your callback(s). - * @return The provided - * callback function is returned unmodified. This is just for convenience if - * you want to pass an inline function to `on()` but store the callback - * function for later passing to `off()`. - */ - on( - eventType: EventType, - callback: (a: firebase.database.DataSnapshot, b?: string | null) => any, - cancelCallbackOrContext?: ((a: Error) => any) | Object | null, - context?: Object | null - ): (a: firebase.database.DataSnapshot | null, b?: string | null) => any; - - /** - * Listens for exactly one event of the specified event type, and then stops - * listening. - * - * This is equivalent to calling {@link firebase.database.Query.on `on()`}, and - * then calling {@link firebase.database.Query.off `off()`} inside the callback - * function. See {@link firebase.database.Query.on `on()`} for details on the - * event types. - * - * @example - * ```javascript - * // Basic usage of .once() to read the data located at ref. - * ref.once('value') - * .then(function(dataSnapshot) { - * // handle read data. - * }); - * ``` - * - * @param eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." - * @param successCallback A - * callback that fires when the specified event occurs. The callback will be - * passed a DataSnapshot. For ordering purposes, "child_added", - * "child_changed", and "child_moved" will also be passed a string containing - * the key of the previous child by sort order, or `null` if it is the - * first child. - * @param failureCallbackOrContext An optional - * callback that will be notified if your client does not have permission to - * read the data. This callback will be passed an `Error` object indicating - * why the failure occurred. - * @param context If provided, this object will be used as `this` - * when calling your callback(s). - */ - once( - eventType: EventType, - successCallback?: ( - a: firebase.database.DataSnapshot, - b?: string | null - ) => any, - failureCallbackOrContext?: ((a: Error) => void) | Object | null, - context?: Object | null - ): Promise; - /** - * Generates a new `Query` object ordered by the specified child key. - * - * Queries can only order by one key at a time. Calling `orderByChild()` - * multiple times on the same query is an error. - * - * Firebase queries allow you to order your data by any child key on the fly. - * However, if you know in advance what your indexes will be, you can define - * them via the .indexOn rule in your Security Rules for better performance. See - * the {@link https://firebase.google.com/docs/database/security/indexing-data - * .indexOn} rule for more information. - * - * You can read more about `orderByChild()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * ```javascript - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").on("child_added", function(snapshot) { - * console.log(snapshot.key + " was " + snapshot.val().height + " m tall"); - * }); - * ``` - */ - orderByChild(path: string): firebase.database.Query; - /** - * Generates a new `Query` object ordered by key. - * - * Sorts the results of a query by their (ascending) key values. - * - * You can read more about `orderByKey()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * ```javascript - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByKey().on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * ``` - */ - orderByKey(): firebase.database.Query; - /** - * Generates a new `Query` object ordered by priority. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data} for alternatives to priority. - */ - orderByPriority(): firebase.database.Query; - /** - * Generates a new `Query` object ordered by value. - * - * If the children of a query are all scalar values (string, number, or - * boolean), you can order the results by their (ascending) values. - * - * You can read more about `orderByValue()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * ```javascript - * var scoresRef = firebase.database().ref("scores"); - * scoresRef.orderByValue().limitToLast(3).on("value", function(snapshot) { - * snapshot.forEach(function(data) { - * console.log("The " + data.key + " score is " + data.val()); - * }); - * }); - * ``` - */ - orderByValue(): firebase.database.Query; - /** - * Returns a `Reference` to the `Query`'s location. - */ - ref: firebase.database.Reference; - /** - * Creates a `Query` with the specified starting point. - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` - * allows you to choose arbitrary starting and ending points for your queries. - * - * The starting point is inclusive, so children with exactly the specified value - * will be included in the query. The optional key argument can be used to - * further limit the range of the query. If it is specified, then children that - * have exactly the specified value must also have a key name greater than or - * equal to the specified key. - * - * You can read more about `startAt()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * ```javascript - * // Find all dinosaurs that are at least three meters tall. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").startAt(3).on("child_added", function(snapshot) { - * console.log(snapshot.key) - * }); - * ``` - * - * @param value The value to start at. The argument - * type depends on which `orderBy*()` function was used in this query. - * Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param key The child key to start at. This argument is only allowed - * if ordering by child, value, or priority. - */ - startAt( - value: number | string | boolean | null, - key?: string - ): firebase.database.Query; - /** - * Creates a `Query` with the specified starting point (exclusive). - * - * Using `startAt()`, `startAfter()`, `endBefore()`, `endAt()` and `equalTo()` - * allows you to choose arbitrary starting and ending points for your queries. - * - * The starting point is exclusive. If only a value is provided, children - * with a value greater than the specified value will be included in the query. - * If a key is specified, then children must have a value greater than or equal - * to the specified value and a a key name greater than the specified key. - * - * @example - * ```javascript - * // Find all dinosaurs that are more than three meters tall. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").startAfter(3).on("child_added", function(snapshot) { - * console.log(snapshot.key) - * }); - * ``` - * - * @param value The value to start after. The argument - * type depends on which `orderBy*()` function was used in this query. - * Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param key The child key to start after. This argument is only allowed - * if ordering by child, value, or priority. - */ - startAfter( - value: number | string | boolean | null, - key?: string - ): firebase.database.Query; - /** - * Returns a JSON-serializable representation of this object. - * - * @return A JSON-serializable representation of this object. - */ - toJSON(): Object; - /** - * Gets the absolute URL for this location. - * - * The `toString()` method returns a URL that is ready to be put into a browser, - * curl command, or a `firebase.database().refFromURL()` call. Since all of - * those expect the URL to be url-encoded, `toString()` returns an encoded URL. - * - * Append '.json' to the returned URL when typed into a browser to download - * JSON-formatted data. If the location is secured (that is, not publicly - * readable), you will get a permission-denied error. - * - * @example - * ```javascript - * // Calling toString() on a root Firebase reference returns the URL where its - * // data is stored within the Database: - * var rootRef = firebase.database().ref(); - * var rootUrl = rootRef.toString(); - * // rootUrl === "https://sample-app.firebaseio.com/". - * - * // Calling toString() at a deeper Firebase reference returns the URL of that - * // deep path within the Database: - * var adaRef = rootRef.child('users/ada'); - * var adaURL = adaRef.toString(); - * // adaURL === "https://sample-app.firebaseio.com/users/ada". - * ``` - * - * @return The absolute URL for this location. - */ - toString(): string; - } - - /** - * A `Reference` represents a specific location in your Database and can be used - * for reading or writing data to that Database location. - * - * You can reference the root or child location in your Database by calling - * `firebase.database().ref()` or `firebase.database().ref("child/path")`. - * - * Writing is done with the `set()` method and reading can be done with the - * `on()` method. See - * {@link - * https://firebase.google.com/docs/database/web/read-and-write - * Read and Write Data on the Web} - */ - interface Reference extends firebase.database.Query { - /** - * Gets a `Reference` for the location at the specified relative path. - * - * The relative path can either be a simple child name (for example, "ada") or - * a deeper slash-separated path (for example, "ada/name/first"). - * - * @example - * ```javascript - * var usersRef = firebase.database().ref('users'); - * var adaRef = usersRef.child('ada'); - * var adaFirstNameRef = adaRef.child('name/first'); - * var path = adaFirstNameRef.toString(); - * // path is now 'https://sample-app.firebaseio.com/users/ada/name/first' - * ``` - * - * @param path A relative path from this location to the desired child - * location. - * @return The specified child location. - */ - child(path: string): firebase.database.Reference; - /** - * The last part of the `Reference`'s path. - * - * For example, `"ada"` is the key for - * `https://.firebaseio.com/users/ada`. - * - * The key of a root `Reference` is `null`. - * - * @example - * ```javascript - * // The key of a root reference is null - * var rootRef = firebase.database().ref(); - * var key = rootRef.key; // key === null - * ``` - * - * @example - * ```javascript - * // The key of any non-root reference is the last token in the path - * var adaRef = firebase.database().ref("users/ada"); - * var key = adaRef.key; // key === "ada" - * key = adaRef.child("name/last").key; // key === "last" - * ``` - */ - key: string | null; - /** - * Returns an `OnDisconnect` object - see - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information on how - * to use it. - */ - onDisconnect(): firebase.database.OnDisconnect; - /** - * The parent location of a `Reference`. - * - * The parent of a root `Reference` is `null`. - * - * @example - * ```javascript - * // The parent of a root reference is null - * var rootRef = firebase.database().ref(); - * parent = rootRef.parent; // parent === null - * ``` - * - * @example - * ```javascript - * // The parent of any non-root reference is the parent location - * var usersRef = firebase.database().ref("users"); - * var adaRef = firebase.database().ref("users/ada"); - * // usersRef and adaRef.parent represent the same location - * ``` - */ - parent: firebase.database.Reference | null; - /** - * Generates a new child location using a unique key and returns its - * `Reference`. - * - * This is the most common pattern for adding data to a collection of items. - * - * If you provide a value to `push()`, the value is written to the - * generated location. If you don't pass a value, nothing is written to the - * database and the child remains empty (but you can use the `Reference` - * elsewhere). - * - * The unique keys generated by `push()` are ordered by the current time, so the - * resulting list of items is chronologically sorted. The keys are also - * designed to be unguessable (they contain 72 random bits of entropy). - * - * - * See - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data - * Append to a list of data} - *
See - * {@link - * https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html - * The 2^120 Ways to Ensure Unique Identifiers} - * - * @example - * ```javascript - * var messageListRef = firebase.database().ref('message_list'); - * var newMessageRef = messageListRef.push(); - * newMessageRef.set({ - * 'user_id': 'ada', - * 'text': 'The Analytical Engine weaves algebraical patterns just as the Jacquard loom weaves flowers and leaves.' - * }); - * // We've appended a new message to the message_list location. - * var path = newMessageRef.toString(); - * // path will be something like - * // 'https://sample-app.firebaseio.com/message_list/-IKo28nwJLH0Nc5XeFmj' - * ``` - * - * @param value Optional value to be written at the generated location. - * @param onComplete Callback called when write to server is - * complete. - * @return Combined `Promise` and `Reference`; resolves when write is complete, but can be - * used immediately as the `Reference` to the child location. - */ - push( - value?: any, - onComplete?: (a: Error | null) => any - ): firebase.database.ThenableReference; - /** - * Removes the data at this Database location. - * - * Any data at child locations will also be deleted. - * - * The effect of the remove will be visible immediately and the corresponding - * event 'value' will be triggered. Synchronization of the remove to the - * Firebase servers will also be started, and the returned Promise will resolve - * when complete. If provided, the onComplete callback will be called - * asynchronously after synchronization has finished. - * - * @example - * ```javascript - * var adaRef = firebase.database().ref('users/ada'); - * adaRef.remove() - * .then(function() { - * console.log("Remove succeeded.") - * }) - * .catch(function(error) { - * console.log("Remove failed: " + error.message) - * }); - * ``` - * - * @param onComplete Callback called when write to server is - * complete. - * @return Resolves when remove on server is complete. - */ - remove(onComplete?: (a: Error | null) => any): Promise; - /** - * The root `Reference` of the Database. - * - * @example - * ```javascript - * // The root of a root reference is itself - * var rootRef = firebase.database().ref(); - * // rootRef and rootRef.root represent the same location - * ``` - * - * @example - * ```javascript - * // The root of any non-root reference is the root location - * var adaRef = firebase.database().ref("users/ada"); - * // rootRef and adaRef.root represent the same location - * ``` - */ - root: firebase.database.Reference; - /** - * Writes data to this Database location. - * - * This will overwrite any data at this location and all child locations. - * - * The effect of the write will be visible immediately, and the corresponding - * events ("value", "child_added", etc.) will be triggered. Synchronization of - * the data to the Firebase servers will also be started, and the returned - * Promise will resolve when complete. If provided, the `onComplete` callback - * will be called asynchronously after synchronization has finished. - * - * Passing `null` for the new value is equivalent to calling `remove()`; namely, - * all data at this location and all child locations will be deleted. - * - * `set()` will remove any priority stored at this location, so if priority is - * meant to be preserved, you need to use `setWithPriority()` instead. - * - * Note that modifying data with `set()` will cancel any pending transactions - * at that location, so extreme care should be taken if mixing `set()` and - * `transaction()` to modify the same data. - * - * A single `set()` will generate a single "value" event at the location where - * the `set()` was performed. - * - * @example - * ```javascript - * var adaNameRef = firebase.database().ref('users/ada/name'); - * adaNameRef.child('first').set('Ada'); - * adaNameRef.child('last').set('Lovelace'); - * // We've written 'Ada' to the Database location storing Ada's first name, - * // and 'Lovelace' to the location storing her last name. - * ``` - * - * @example - * ```javascript - * adaNameRef.set({ first: 'Ada', last: 'Lovelace' }); - * // Exact same effect as the previous example, except we've written - * // Ada's first and last name simultaneously. - * ``` - * - * @example - * ```javascript - * adaNameRef.set({ first: 'Ada', last: 'Lovelace' }) - * .then(function() { - * console.log('Synchronization succeeded'); - * }) - * .catch(function(error) { - * console.log('Synchronization failed'); - * }); - * // Same as the previous example, except we will also log a message - * // when the data has finished synchronizing. - * ``` - * - * @param value The value to be written (string, number, boolean, object, - * array, or null). - * @param onComplete Callback called when write to server is - * complete. - * @return Resolves when write to server is complete. - */ - set(value: any, onComplete?: (a: Error | null) => any): Promise; - /** - * Sets a priority for the data at this Database location. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - */ - setPriority( - priority: string | number | null, - onComplete: (a: Error | null) => any - ): Promise; - /** - * Writes data the Database location. Like `set()` but also specifies the - * priority for that data. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - */ - setWithPriority( - newVal: any, - newPriority: string | number | null, - onComplete?: (a: Error | null) => any - ): Promise; - /** - * Atomically modifies the data at this location. - * - * Atomically modify the data at this location. Unlike a normal `set()`, which - * just overwrites the data regardless of its previous value, `transaction()` is - * used to modify the existing value to a new value, ensuring there are no - * conflicts with other clients writing to the same location at the same time. - * - * To accomplish this, you pass `transaction()` an update function which is used - * to transform the current value into a new value. If another client writes to - * the location before your new value is successfully written, your update - * function will be called again with the new current value, and the write will - * be retried. This will happen repeatedly until your write succeeds without - * conflict or you abort the transaction by not returning a value from your - * update function. - * - * Note: Modifying data with `set()` will cancel any pending transactions at - * that location, so extreme care should be taken if mixing `set()` and - * `transaction()` to update the same data. - * - * Note: When using transactions with Security and Firebase Rules in place, be - * aware that a client needs `.read` access in addition to `.write` access in - * order to perform a transaction. This is because the client-side nature of - * transactions requires the client to read the data in order to transactionally - * update it. - * - * @example - * ```javascript - * // Increment Ada's rank by 1. - * var adaRankRef = firebase.database().ref('users/ada/rank'); - * adaRankRef.transaction(function(currentRank) { - * // If users/ada/rank has never been set, currentRank will be `null`. - * return currentRank + 1; - * }); - * ``` - * - * @example - * ```javascript - * // Try to create a user for ada, but only if the user id 'ada' isn't - * // already taken - * var adaRef = firebase.database().ref('users/ada'); - * adaRef.transaction(function(currentData) { - * if (currentData === null) { - * return { name: { first: 'Ada', last: 'Lovelace' } }; - * } else { - * console.log('User ada already exists.'); - * return; // Abort the transaction. - * } - * }, function(error, committed, snapshot) { - * if (error) { - * console.log('Transaction failed abnormally!', error); - * } else if (!committed) { - * console.log('We aborted the transaction (because ada already exists).'); - * } else { - * console.log('User ada added!'); - * } - * console.log("Ada's data: ", snapshot.val()); - * }); - * ``` - * - * @param transactionUpdate A developer-supplied function which - * will be passed the current data stored at this location (as a JavaScript - * object). The function should return the new value it would like written (as - * a JavaScript object). If `undefined` is returned (i.e. you return with no - * arguments) the transaction will be aborted and the data at this location - * will not be modified. - * @param onComplete A callback - * function that will be called when the transaction completes. The callback - * is passed three arguments: a possibly-null `Error`, a `boolean` indicating - * whether the transaction was committed, and a `DataSnapshot` indicating the - * final result. If the transaction failed abnormally, the first argument will - * be an `Error` object indicating the failure cause. If the transaction - * finished normally, but no data was committed because no data was returned - * from `transactionUpdate`, then second argument will be false. If the - * transaction completed and committed data to Firebase, the second argument - * will be true. Regardless, the third argument will be a `DataSnapshot` - * containing the resulting data in this location. - * @param applyLocally By default, events are raised each time the - * transaction update function runs. So if it is run multiple times, you may - * see intermediate states. You can set this to false to suppress these - * intermediate states and instead wait until the transaction has completed - * before events are raised. - * @return Returns a Promise that can optionally be used instead of the onComplete - * callback to handle success and failure. - */ - transaction( - transactionUpdate: (a: any) => any, - onComplete?: ( - a: Error | null, - b: boolean, - c: firebase.database.DataSnapshot | null - ) => any, - applyLocally?: boolean - ): Promise; - /** - * Writes multiple values to the Database at once. - * - * The `values` argument contains multiple property-value pairs that will be - * written to the Database together. Each child property can either be a simple - * property (for example, "name") or a relative path (for example, - * "name/first") from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update - * only the referenced properties at the current location (instead of replacing - * all the child properties at the current location). - * - * The effect of the write will be visible immediately, and the corresponding - * events ('value', 'child_added', etc.) will be triggered. Synchronization of - * the data to the Firebase servers will also be started, and the returned - * Promise will resolve when complete. If provided, the `onComplete` callback - * will be called asynchronously after synchronization has finished. - * - * A single `update()` will generate a single "value" event at the location - * where the `update()` was performed, regardless of how many children were - * modified. - * - * Note that modifying data with `update()` will cancel any pending - * transactions at that location, so extreme care should be taken if mixing - * `update()` and `transaction()` to modify the same data. - * - * Passing `null` to `update()` will remove the data at this location. - * - * See - * {@link - * https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html - * Introducing multi-location updates and more}. - * - * @example - * ```javascript - * var adaNameRef = firebase.database().ref('users/ada/name'); - * // Modify the 'first' and 'last' properties, but leave other data at - * // adaNameRef unchanged. - * adaNameRef.update({ first: 'Ada', last: 'Lovelace' }); - * ``` - * - * @param values Object containing multiple values. - * @param onComplete Callback called when write to server is - * complete. - * @return Resolves when update on server is complete. - */ - update(values: Object, onComplete?: (a: Error | null) => any): Promise; - } - - interface ThenableReference - extends firebase.database.Reference, - Pick, 'then' | 'catch'> {} - - /** - * Logs debugging information to the console. - * - * @example - * ```javascript - * // Enable logging - * firebase.database.enableLogging(true); - * ``` - * - * @example - * ```javascript - * // Disable logging - * firebase.database.enableLogging(false); - * ``` - * - * @example - * ```javascript - * // Enable logging across page refreshes - * firebase.database.enableLogging(true, true); - * ``` - * - * @example - * ```javascript - * // Provide custom logger which prefixes log statements with "[FIREBASE]" - * firebase.database.enableLogging(function(message) { - * console.log("[FIREBASE]", message); - * }); - * ``` - * - * @param logger Enables logging if `true`; - * disables logging if `false`. You can also provide a custom logger function - * to control how things get logged. - * @param persistent Remembers the logging state between page - * refreshes if `true`. - */ - function enableLogging( - logger?: boolean | ((a: string) => any), - persistent?: boolean - ): any; -} - -declare namespace firebase.database.ServerValue { - /** - * A placeholder value for auto-populating the current timestamp (time - * since the Unix epoch, in milliseconds) as determined by the Firebase - * servers. - * - * @example - * ```javascript - * var sessionsRef = firebase.database().ref("sessions"); - * sessionsRef.push({ - * startedAt: firebase.database.ServerValue.TIMESTAMP - * }); - * ``` - */ - var TIMESTAMP: Object; - - /** - * Returns a placeholder value that can be used to atomically increment the - * current database value by the provided delta. - * - * @param delta the amount to modify the current value atomically. - * @return a placeholder value for modifying data atomically server-side. - */ - function increment(delta: number): Object; -} - -/** - * @webonly - */ -declare namespace firebase.messaging { - /** - * The Firebase Messaging service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.messaging `firebase.messaging()`}. - * - * See {@link https://firebase.google.com/docs/cloud-messaging/js/client - * Set Up a JavaScript Firebase Cloud Messaging Client App} for a full guide on how to use the - * Firebase Messaging service. - * - */ - interface Messaging { - /** - * Deletes the registration token associated with this messaging instance and unsubscribes the - * messaging instance from the push subscription. - * - * @return The promise resolves when the token has been successfully deleted. - */ - deleteToken(): Promise; - - /** - * Subscribes the messaging instance to push notifications. Returns an FCM registration token - * that can be used to send push messages to that messaging instance. - * - * If a notification permission isn't already granted, this method asks the user for permission. - * The returned promise rejects if the user does not allow the app to show notifications. - * - * @param options.vapidKey The public server key provided to push services. It is used to - * authenticate the push subscribers to receive push messages only from sending servers that - * hold the corresponding private key. If it is not provided, a default VAPID key is used. Note - * that some push services (Chrome Push Service) require a non-default VAPID key. Therefore, it - * is recommended to generate and import a VAPID key for your project with - * {@link https://firebase.google.com/docs/cloud-messaging/js/client#configure_web_credentials_with_fcm Configure Web Credentials with FCM}. - * See - * {@link https://developers.google.com/web/fundamentals/push-notifications/web-push-protocol The Web Push Protocol} - * for details on web push services.} - * - * @param options.serviceWorkerRegistration The service worker registration for receiving push - * messaging. If the registration is not provided explicitly, you need to have a - * `firebase-messaging-sw.js` at your root location. See - * {@link https://firebase.google.com/docs/cloud-messaging/js/client#retrieve-the-current-registration-token Retrieve the current registration token} - * for more details. - * - * @return The promise resolves with an FCM registration token. - * - */ - getToken(options?: { - vapidKey?: string; - serviceWorkerRegistration?: ServiceWorkerRegistration; - }): Promise; - - /** - * When a push message is received and the user is currently on a page for your origin, the - * message is passed to the page and an `onMessage()` event is dispatched with the payload of - * the push message. - * - * @param - * nextOrObserver This function, or observer object with `next` defined, - * is called when a message is received and the user is currently viewing your page. - * @return To stop listening for messages execute this returned function. - */ - onMessage( - nextOrObserver: - | firebase.NextFn - | firebase.Observer - ): firebase.Unsubscribe; - - /** - * Called when a message is received while the app is in the background. An app is considered to - * be in the background if no active window is displayed. - * - * @param - * nextOrObserver This function, or observer object with `next` defined, - * is called when a message is received and the app is currently in the background. - * - * @return To stop listening for messages execute this returned function - */ - onBackgroundMessage( - nextOrObserver: - | firebase.NextFn - | firebase.Observer - ): firebase.Unsubscribe; - } - - /** - * Message payload that contains the notification payload that is represented with - * {@link firebase.messaging.NotificationPayload} and the data payload that contains an arbitrary - * number of key-value pairs sent by developers through the - * {@link https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#notification Send API} - */ - export interface MessagePayload { - /** - * See {@link firebase.messaging.NotificationPayload}. - */ - notification?: NotificationPayload; - - /** - * Arbitrary key/value pairs. - */ - data?: { [key: string]: string }; - - /** - * See {@link firebase.messaging.FcmOptions}. - */ - fcmOptions?: FcmOptions; - - /** - * The sender of this message. - */ - from: string; - - /** - * The collapse key of this message. See - * {@link https://firebase.google.com/docs/cloud-messaging/concept-options#collapsible_and_non-collapsible_messages - * Collapsible and non-collapsible messages}. - */ - collapseKey: string; - } - - /** - * Options for features provided by the FCM SDK for Web. See - * {@link https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#webpushfcmoptions - * WebpushFcmOptions}. - */ - export interface FcmOptions { - /** - * The link to open when the user clicks on the notification. For all URL values, HTTPS is - * required. For example, by setting this value to your app's URL, a notification click event - * will put your app in focus for the user. - */ - link?: string; - - /** - * Label associated with the message's analytics data. See - * {@link https://firebase.google.com/docs/cloud-messaging/understand-delivery#adding-analytics-labels-to-messages - * Adding analytics labels}. - */ - analyticsLabel?: string; - } - - /** - * Parameters that define how a push notification is displayed to users. - */ - export interface NotificationPayload { - /** - * The title of a notification. - */ - title?: string; - - /** - * The body of a notification. - */ - body?: string; - - /** - * The URL of the image that is shown with the notification. See - * {@link https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#notification - * `notification.image`} for supported image format. - */ - image?: string; - } - - function isSupported(): boolean; -} - -declare namespace firebase.storage { - /** - * The full set of object metadata, including read-only properties. - */ - interface FullMetadata extends firebase.storage.UploadMetadata { - /** - * The bucket this object is contained in. - */ - bucket: string; - /** - * The full path of this object. - */ - fullPath: string; - /** - * The object's generation. - * @see {@link https://cloud.google.com/storage/docs/generations-preconditions} - */ - generation: string; - /** - * The object's metageneration. - * @see {@link https://cloud.google.com/storage/docs/generations-preconditions} - */ - metageneration: string; - /** - * The short name of this object, which is the last component of the full path. - * For example, if fullPath is 'full/path/image.png', name is 'image.png'. - */ - name: string; - /** - * The size of this object, in bytes. - */ - size: number; - /** - * A date string representing when this object was created. - */ - timeCreated: string; - /** - * A date string representing when this object was last updated. - */ - updated: string; - } - - /** - * Represents a reference to a Google Cloud Storage object. Developers can - * upload, download, and delete objects, as well as get/set object metadata. - */ - interface Reference { - /** - * The name of the bucket containing this reference's object. - */ - bucket: string; - /** - * Returns a reference to a relative path from this reference. - * @param path The relative path from this reference. - * Leading, trailing, and consecutive slashes are removed. - * @return The reference to the given path. - */ - child(path: string): firebase.storage.Reference; - /** - * Deletes the object at this reference's location. - * @return A Promise that resolves if the deletion - * succeeded and rejects if it failed, including if the object didn't exist. - */ - delete(): Promise; - /** - * The full path of this object. - */ - fullPath: string; - /** - * Fetches a long lived download URL for this object. - * @return A Promise that resolves with the download - * URL or rejects if the fetch failed, including if the object did not - * exist. - */ - getDownloadURL(): Promise; - /** - * Fetches metadata for the object at this location, if one exists. - * @return A Promise that - * resolves with the metadata, or rejects if the fetch failed, including if - * the object did not exist. - */ - getMetadata(): Promise; - /** - * The short name of this object, which is the last component of the full path. - * For example, if fullPath is 'full/path/image.png', name is 'image.png'. - */ - name: string; - /** - * A reference pointing to the parent location of this reference, or null if - * this reference is the root. - */ - parent: firebase.storage.Reference | null; - /** - * Uploads data to this reference's location. - * @param data The data to upload. - * @param metadata Metadata for the newly - * uploaded object. - * @return An object that can be used to monitor - * and manage the upload. - */ - put( - data: Blob | Uint8Array | ArrayBuffer, - metadata?: firebase.storage.UploadMetadata - ): firebase.storage.UploadTask; - /** - * Uploads string data to this reference's location. - * @param data The string to upload. - * @param format The format of the string to - * upload. - * @param metadata Metadata for the newly - * uploaded object. - * @throws If the format is not an allowed format, or if the given string - * doesn't conform to the specified format. - */ - putString( - data: string, - format?: firebase.storage.StringFormat, - metadata?: firebase.storage.UploadMetadata - ): firebase.storage.UploadTask; - /** - * A reference to the root of this reference's bucket. - */ - root: firebase.storage.Reference; - /** - * The storage service associated with this reference. - */ - storage: firebase.storage.Storage; - /** - * Returns a gs:// URL for this object in the form - * `gs://///` - * @return The gs:// URL. - */ - toString(): string; - /** - * Updates the metadata for the object at this location, if one exists. - * @param metadata The new metadata. - * Setting a property to 'null' removes it on the server, while leaving - * a property as 'undefined' has no effect. - * @return A Promise that - * resolves with the full updated metadata or rejects if the updated failed, - * including if the object did not exist. - */ - updateMetadata( - metadata: firebase.storage.SettableMetadata - ): Promise; - /** - * List all items (files) and prefixes (folders) under this storage reference. - * - * This is a helper method for calling `list()` repeatedly until there are - * no more results. The default pagination size is 1000. - * - * Note: The results may not be consistent if objects are changed while this - * operation is running. - * - * Warning: `listAll` may potentially consume too many resources if there are - * too many results. - * - * @return A Promise that resolves with all the items and prefixes under - * the current storage reference. `prefixes` contains references to - * sub-directories and `items` contains references to objects in this - * folder. `nextPageToken` is never returned. - */ - listAll(): Promise; - /** - * List items (files) and prefixes (folders) under this storage reference. - * - * List API is only available for Firebase Rules Version 2. - * - * GCS is a key-blob store. Firebase Storage imposes the semantic of '/' - * delimited folder structure. - * Refer to GCS's List API if you want to learn more. - * - * To adhere to Firebase Rules's Semantics, Firebase Storage does not - * support objects whose paths end with "/" or contain two consecutive - * "/"s. Firebase Storage List API will filter these unsupported objects. - * `list()` may fail if there are too many unsupported objects in the bucket. - * - * @param options See `ListOptions` for details. - * @return A Promise that resolves with the items and prefixes. - * `prefixes` contains references to sub-folders and `items` - * contains references to objects in this folder. `nextPageToken` - * can be used to get the rest of the results. - */ - list(options?: ListOptions): Promise; - } - - /** - * Result returned by list(). - */ - interface ListResult { - /** - * References to prefixes (sub-folders). You can call list() on them to - * get its contents. - * - * Folders are implicit based on '/' in the object paths. - * For example, if a bucket has two objects '/a/b/1' and '/a/b/2', list('/a') - * will return '/a/b' as a prefix. - */ - prefixes: Reference[]; - /** - * Objects in this directory. - * You can call getMetadata() and getDownloadUrl() on them. - */ - items: Reference[]; - /** - * If set, there might be more results for this list. Use this token to resume the list. - */ - nextPageToken: string | null; - } - - /** - * The options `list()` accepts. - */ - interface ListOptions { - /** - * If set, limits the total number of `prefixes` and `items` to return. - * The default and maximum maxResults is 1000. - */ - maxResults?: number | null; - /** - * The `nextPageToken` from a previous call to `list()`. If provided, - * listing is resumed from the previous position. - */ - pageToken?: string | null; - } - - /** - * Object metadata that can be set at any time. - */ - interface SettableMetadata { - /** - * Served as the 'Cache-Control' header on object download. - */ - cacheControl?: string | null; - contentDisposition?: string | null; - /** - * Served as the 'Content-Encoding' header on object download. - */ - contentEncoding?: string | null; - /** - * Served as the 'Content-Language' header on object download. - */ - contentLanguage?: string | null; - /** - * Served as the 'Content-Type' header on object download. - */ - contentType?: string | null; - /** - * Additional user-defined custom metadata. - */ - customMetadata?: { - [/* warning: coerced from ? */ key: string]: string; - } | null; - } - - /** - * The Firebase Storage service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.storage `firebase.storage()`}. - * - * See - * {@link - * https://firebase.google.com/docs/storage/web/start/ - * Get Started on Web} - * for a full guide on how to use the Firebase Storage service. - */ - interface Storage { - /** - * The {@link firebase.app.App app} associated with the `Storage` service - * instance. - * - * @example - * ```javascript - * var app = storage.app; - * ``` - */ - app: firebase.app.App; - /** - * The maximum time to retry operations other than uploads or downloads in - * milliseconds. - */ - maxOperationRetryTime: number; - /** - * The maximum time to retry uploads in milliseconds. - */ - maxUploadRetryTime: number; - /** - * Returns a reference for the given path in the default bucket. - * @param path A relative path to initialize the reference with, - * for example `path/to/image.jpg`. If not passed, the returned reference - * points to the bucket root. - * @return A reference for the given path. - */ - ref(path?: string): firebase.storage.Reference; - /** - * Returns a reference for the given absolute URL. - * @param url A URL in the form:
- * 1) a gs:// URL, for example `gs://bucket/files/image.png`
- * 2) a download URL taken from object metadata.
- * @return A reference for the given URL. - */ - refFromURL(url: string): firebase.storage.Reference; - /** - * @param time The new maximum operation retry time in milliseconds. - * @see {@link firebase.storage.Storage.maxOperationRetryTime} - */ - setMaxOperationRetryTime(time: number): any; - /** - * @param time The new maximum upload retry time in milliseconds. - * @see {@link firebase.storage.Storage.maxUploadRetryTime} - */ - setMaxUploadRetryTime(time: number): any; - /** - * Modify this `Storage` instance to communicate with the Cloud Storage emulator. - * - * @param host - The emulator host (ex: localhost) - * @param port - The emulator port (ex: 5001) - */ - useEmulator(host: string, port: number): void; - } - - /** - * @enum {string} - * An enumeration of the possible string formats for upload. - */ - type StringFormat = string; - var StringFormat: { - /** - * Indicates the string should be interpreted as base64-encoded data. - * Padding characters (trailing '='s) are optional. - * Example: The string 'rWmO++E6t7/rlw==' becomes the byte sequence - * ad 69 8e fb e1 3a b7 bf eb 97 - */ - BASE64: StringFormat; - /** - * Indicates the string should be interpreted as base64url-encoded data. - * Padding characters (trailing '='s) are optional. - * Example: The string 'rWmO--E6t7_rlw==' becomes the byte sequence - * ad 69 8e fb e1 3a b7 bf eb 97 - */ - BASE64URL: StringFormat; - /** - * Indicates the string is a data URL, such as one obtained from - * canvas.toDataURL(). - * Example: the string 'data:application/octet-stream;base64,aaaa' - * becomes the byte sequence - * 69 a6 9a - * (the content-type "application/octet-stream" is also applied, but can - * be overridden in the metadata object). - */ - DATA_URL: StringFormat; - /** - * Indicates the string should be interpreted "raw", that is, as normal text. - * The string will be interpreted as UTF-16, then uploaded as a UTF-8 byte - * sequence. - * Example: The string 'Hello! \ud83d\ude0a' becomes the byte sequence - * 48 65 6c 6c 6f 21 20 f0 9f 98 8a - */ - RAW: StringFormat; - }; - - /** - * An event that is triggered on a task. - * @enum {string} - * @see {@link firebase.storage.UploadTask.on} - */ - type TaskEvent = string; - var TaskEvent: { - /** - * For this event, - *
    - *
  • The `next` function is triggered on progress updates and when the - * task is paused/resumed with a - * {@link firebase.storage.UploadTaskSnapshot} as the first - * argument.
  • - *
  • The `error` function is triggered if the upload is canceled or fails - * for another reason.
  • - *
  • The `complete` function is triggered if the upload completes - * successfully.
  • - *
- */ - STATE_CHANGED: TaskEvent; - }; - - /** - * Represents the current state of a running upload. - * @enum {string} - */ - type TaskState = string; - var TaskState: { - CANCELED: TaskState; - ERROR: TaskState; - PAUSED: TaskState; - RUNNING: TaskState; - SUCCESS: TaskState; - }; - - /** - * Object metadata that can be set at upload. - */ - interface UploadMetadata extends firebase.storage.SettableMetadata { - /** - * A Base64-encoded MD5 hash of the object being uploaded. - */ - md5Hash?: string | null; - } - - /** - * An error returned by the Firebase Storage SDK. - */ - interface FirebaseStorageError extends FirebaseError { - serverResponse: string | null; - } - - interface StorageObserver { - next?: NextFn | null; - error?: (error: FirebaseStorageError) => void | null; - complete?: CompleteFn | null; - } - - /** - * Represents the process of uploading an object. Allows you to monitor and - * manage the upload. - */ - interface UploadTask { - /** - * Cancels a running task. Has no effect on a complete or failed task. - * @return True if the cancel had an effect. - */ - cancel(): boolean; - /** - * Equivalent to calling `then(null, onRejected)`. - */ - catch(onRejected: (error: FirebaseStorageError) => any): Promise; - /** - * Listens for events on this task. - * - * Events have three callback functions (referred to as `next`, `error`, and - * `complete`). - * - * If only the event is passed, a function that can be used to register the - * callbacks is returned. Otherwise, the callbacks are passed after the event. - * - * Callbacks can be passed either as three separate arguments or as the - * `next`, `error`, and `complete` properties of an object. Any of the three - * callbacks is optional, as long as at least one is specified. In addition, - * when you add your callbacks, you get a function back. You can call this - * function to unregister the associated callbacks. - * - * @example **Pass callbacks separately or in an object.** - * ```javascript - * var next = function(snapshot) {}; - * var error = function(error) {}; - * var complete = function() {}; - * - * // The first example. - * uploadTask.on( - * firebase.storage.TaskEvent.STATE_CHANGED, - * next, - * error, - * complete); - * - * // This is equivalent to the first example. - * uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, { - * 'next': next, - * 'error': error, - * 'complete': complete - * }); - * - * // This is equivalent to the first example. - * var subscribe = uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED); - * subscribe(next, error, complete); - * - * // This is equivalent to the first example. - * var subscribe = uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED); - * subscribe({ - * 'next': next, - * 'error': error, - * 'complete': complete - * }); - * ``` - * - * @example **Any callback is optional.** - * ```javascript - * // Just listening for completion, this is legal. - * uploadTask.on( - * firebase.storage.TaskEvent.STATE_CHANGED, - * null, - * null, - * function() { - * console.log('upload complete!'); - * }); - * - * // Just listening for progress/state changes, this is legal. - * uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, function(snapshot) { - * var percent = snapshot.bytesTransferred / snapshot.totalBytes * 100; - * console.log(percent + "% done"); - * }); - * - * // This is also legal. - * uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, { - * 'complete': function() { - * console.log('upload complete!'); - * } - * }); - * ``` - * - * @example **Use the returned function to remove callbacks.** - * ```javascript - * var unsubscribe = uploadTask.on( - * firebase.storage.TaskEvent.STATE_CHANGED, - * function(snapshot) { - * var percent = snapshot.bytesTransferred / snapshot.totalBytes * 100; - * console.log(percent + "% done"); - * // Stop after receiving one update. - * unsubscribe(); - * }); - * - * // This code is equivalent to the above. - * var handle = uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED); - * unsubscribe = handle(function(snapshot) { - * var percent = snapshot.bytesTransferred / snapshot.totalBytes * 100; - * console.log(percent + "% done"); - * // Stop after receiving one update. - * unsubscribe(); - * }); - * ``` - * - * @param event The event to listen for. - * @param nextOrObserver - * The `next` function, which gets called for each item in - * the event stream, or an observer object with some or all of these three - * properties (`next`, `error`, `complete`). - * @param error A function that gets called with a `FirebaseStorageError` - * if the event stream ends due to an error. - * @param complete A function that gets called if the - * event stream ends normally. - * @return - * If only the event argument is passed, returns a function you can use to - * add callbacks (see the examples above). If more than just the event - * argument is passed, returns a function you can call to unregister the - * callbacks. - */ - on( - event: firebase.storage.TaskEvent, - nextOrObserver?: - | StorageObserver - | null - | ((snapshot: UploadTaskSnapshot) => any), - error?: ((error: FirebaseStorageError) => any) | null, - complete?: firebase.Unsubscribe | null - ): Function; - /** - * Pauses a running task. Has no effect on a paused or failed task. - * @return True if the pause had an effect. - */ - pause(): boolean; - /** - * Resumes a paused task. Has no effect on a running or failed task. - * @return True if the resume had an effect. - */ - resume(): boolean; - /** - * A snapshot of the current task state. - */ - snapshot: firebase.storage.UploadTaskSnapshot; - /** - * This object behaves like a Promise, and resolves with its snapshot data when - * the upload completes. - * @param onFulfilled - * The fulfillment callback. Promise chaining works as normal. - * @param onRejected The rejection callback. - */ - then( - onFulfilled?: - | ((snapshot: firebase.storage.UploadTaskSnapshot) => any) - | null, - onRejected?: ((error: FirebaseStorageError) => any) | null - ): Promise; - } - - /** - * Holds data about the current state of the upload task. - */ - interface UploadTaskSnapshot { - /** - * The number of bytes that have been successfully uploaded so far. - */ - bytesTransferred: number; - /** - * Before the upload completes, contains the metadata sent to the server. - * After the upload completes, contains the metadata sent back from the server. - */ - metadata: firebase.storage.FullMetadata; - /** - * The reference that spawned this snapshot's upload task. - */ - ref: firebase.storage.Reference; - /** - * The current state of the task. - */ - state: firebase.storage.TaskState; - /** - * The task of which this is a snapshot. - */ - task: firebase.storage.UploadTask; - /** - * The total number of bytes to be uploaded. - */ - totalBytes: number; - } -} - -declare namespace firebase.firestore { - /** - * Document data (for use with `DocumentReference.set()`) consists of fields - * mapped to values. - */ - export type DocumentData = { [field: string]: any }; - - /** - * Update data (for use with `DocumentReference.update()`) consists of field - * paths (e.g. 'foo' or 'foo.baz') mapped to values. Fields that contain dots - * reference nested fields within the document. - */ - export type UpdateData = { [fieldPath: string]: any }; - - /** - * Constant used to indicate the LRU garbage collection should be disabled. - * Set this value as the `cacheSizeBytes` on the settings passed to the - * `Firestore` instance. - */ - export const CACHE_SIZE_UNLIMITED: number; - - /** - * Specifies custom configurations for your Cloud Firestore instance. - * You must set these before invoking any other methods. - */ - export interface Settings { - /** The hostname to connect to. */ - host?: string; - /** Whether to use SSL when connecting. */ - ssl?: boolean; - - /** - * An approximate cache size threshold for the on-disk data. If the cache grows beyond this - * size, Firestore will start removing data that hasn't been recently used. The size is not a - * guarantee that the cache will stay below that size, only that if the cache exceeds the given - * size, cleanup will be attempted. - * - * The default value is 40 MB. The threshold must be set to at least 1 MB, and can be set to - * CACHE_SIZE_UNLIMITED to disable garbage collection. - */ - cacheSizeBytes?: number; - - /** - * Forces the SDK’s underlying network transport (WebChannel) to use - * long-polling. Each response from the backend will be closed immediately - * after the backend sends data (by default responses are kept open in - * case the backend has more data to send). This avoids incompatibility - * issues with certain proxies, antivirus software, etc. that incorrectly - * buffer traffic indefinitely. Use of this option will cause some - * performance degradation though. - * - * This setting cannot be used with `experimentalAutoDetectLongPolling` and - * may be removed in a future release. If you find yourself using it to - * work around a specific network reliability issue, please tell us about - * it in https://github.com/firebase/firebase-js-sdk/issues/1674. - * - * @webonly - */ - experimentalForceLongPolling?: boolean; - - /** - * Configures the SDK's underlying transport (WebChannel) to automatically detect if - * long-polling should be used. This is very similar to `experimentalForceLongPolling`, - * but only uses long-polling if required. - * - * This setting will likely be enabled by default in future releases and cannot be - * combined with `experimentalForceLongPolling`. - * - * @webonly - */ - experimentalAutoDetectLongPolling?: boolean; - - /** - * Whether to skip nested properties that are set to `undefined` during - * object serialization. If set to `true`, these properties are skipped - * and not written to Firestore. If set to `false` or omitted, the SDK - * throws an exception when it encounters properties of type `undefined`. - */ - ignoreUndefinedProperties?: boolean; - - /** - * Whether to merge the provided settings with the existing settings. If - * set to `true`, the settings are merged with existing settings. If - * set to `false` or left unset, the settings replace the existing - * settings. - */ - merge?: boolean; - } - - /** - * Settings that can be passed to Firestore.enablePersistence() to configure - * Firestore persistence. - */ - export interface PersistenceSettings { - /** - * Whether to synchronize the in-memory state of multiple tabs. Setting this - * to `true` in all open tabs enables shared access to local persistence, - * shared execution of queries and latency-compensated local document updates - * across all connected instances. - * - * To enable this mode, `synchronizeTabs:true` needs to be set globally in all - * active tabs. If omitted or set to 'false', `enablePersistence()` will fail - * in all but the first tab. - */ - synchronizeTabs?: boolean; - - /** - * Whether to force enable persistence for the client. This cannot be used - * with `synchronizeTabs:true` and is primarily intended for use with Web - * Workers. Setting this to `true` will enable persistence, but cause other - * tabs using persistence to fail. - * - * This setting may be removed in a future release. If you find yourself - * using it for a specific use case or run into any issues, please tell us - * about it in - * https://github.com/firebase/firebase-js-sdk/issues/983. - */ - experimentalForceOwningTab?: boolean; - } - - export type LogLevel = 'debug' | 'error' | 'silent'; - - /** - * Sets the verbosity of Cloud Firestore logs (debug, error, or silent). - * - * @param logLevel - * The verbosity you set for activity and error logging. Can be any of - * the following values: - * - *
    - *
  • debug for the most verbose logging level, primarily for - * debugging.
  • - *
  • error to log errors only.
  • - *
  • silent to turn off logging.
  • - *
- */ - export function setLogLevel(logLevel: LogLevel): void; - - /** - * Converter used by `withConverter()` to transform user objects of type T - * into Firestore data. - * - * Using the converter allows you to specify generic type arguments when - * storing and retrieving objects from Firestore. - * - * @example - * ```typescript - * class Post { - * constructor(readonly title: string, readonly author: string) {} - * - * toString(): string { - * return this.title + ', by ' + this.author; - * } - * } - * - * const postConverter = { - * toFirestore(post: Post): firebase.firestore.DocumentData { - * return {title: post.title, author: post.author}; - * }, - * fromFirestore( - * snapshot: firebase.firestore.QueryDocumentSnapshot, - * options: firebase.firestore.SnapshotOptions - * ): Post { - * const data = snapshot.data(options)!; - * return new Post(data.title, data.author); - * } - * }; - * - * const postSnap = await firebase.firestore() - * .collection('posts') - * .withConverter(postConverter) - * .doc().get(); - * const post = postSnap.data(); - * if (post !== undefined) { - * post.title; // string - * post.toString(); // Should be defined - * post.someNonExistentProperty; // TS error - * } - * ``` - */ - export interface FirestoreDataConverter { - /** - * Called by the Firestore SDK to convert a custom model object of type T - * into a plain Javascript object (suitable for writing directly to the - * Firestore database). To use `set()` with `merge` and `mergeFields`, - * `toFirestore()` must be defined with `Partial`. - */ - toFirestore(modelObject: T): DocumentData; - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; - - /** - * Called by the Firestore SDK to convert Firestore data into an object of - * type T. You can access your data by calling: `snapshot.data(options)`. - * - * @param snapshot A QueryDocumentSnapshot containing your data and metadata. - * @param options The SnapshotOptions from the initial call to `data()`. - */ - fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions): T; - } - - /** - * The Cloud Firestore service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.firestore `firebase.firestore()`}. - */ - export class Firestore { - private constructor(); - /** - * Specifies custom settings to be used to configure the `Firestore` - * instance. Must be set before invoking any other methods. - * - * @param settings The settings to use. - */ - settings(settings: Settings): void; - - /** - * Modify this instance to communicate with the Cloud Firestore emulator. - * - *

Note: this must be called before this instance has been used to do any operations. - * - * @param host the emulator host (ex: localhost). - * @param port the emulator port (ex: 9000). - */ - useEmulator(host: string, port: number): void; - - /** - * Attempts to enable persistent storage, if possible. - * - * Must be called before any other methods (other than settings() and - * clearPersistence()). - * - * If this fails, enablePersistence() will reject the promise it returns. - * Note that even after this failure, the firestore instance will remain - * usable, however offline persistence will be disabled. - * - * There are several reasons why this can fail, which can be identified by - * the `code` on the error. - * - * * failed-precondition: The app is already open in another browser tab. - * * unimplemented: The browser is incompatible with the offline - * persistence implementation. - * - * @param settings Optional settings object to configure persistence. - * @return A promise that represents successfully enabling persistent - * storage. - */ - enablePersistence(settings?: PersistenceSettings): Promise; - - /** - * Gets a `CollectionReference` instance that refers to the collection at - * the specified path. - * - * @param collectionPath A slash-separated path to a collection. - * @return The `CollectionReference` instance. - */ - collection(collectionPath: string): CollectionReference; - - /** - * Gets a `DocumentReference` instance that refers to the document at the - * specified path. - * - * @param documentPath A slash-separated path to a document. - * @return The `DocumentReference` instance. - */ - doc(documentPath: string): DocumentReference; - - /** - * Creates and returns a new Query that includes all documents in the - * database that are contained in a collection or subcollection with the - * given collectionId. - * - * @param collectionId Identifies the collections to query over. Every - * collection or subcollection with this ID as the last segment of its path - * will be included. Cannot contain a slash. - * @return The created Query. - */ - collectionGroup(collectionId: string): Query; - - /** - * Executes the given `updateFunction` and then attempts to commit the changes - * applied within the transaction. If any document read within the transaction - * has changed, Cloud Firestore retries the `updateFunction`. If it fails to - * commit after 5 attempts, the transaction fails. - * - * The maximum number of writes allowed in a single transaction is 500, but - * note that each usage of `FieldValue.serverTimestamp()`, - * `FieldValue.arrayUnion()`, `FieldValue.arrayRemove()`, or - * `FieldValue.increment()` inside a transaction counts as an additional write. - * - * @param updateFunction - * The function to execute within the transaction context. - * - * @return - * If the transaction completed successfully or was explicitly aborted - * (the `updateFunction` returned a failed promise), - * the promise returned by the updateFunction is returned here. Else, if the - * transaction failed, a rejected promise with the corresponding failure - * error will be returned. - */ - runTransaction( - updateFunction: (transaction: Transaction) => Promise - ): Promise; - - /** - * Creates a write batch, used for performing multiple writes as a single - * atomic operation. The maximum number of writes allowed in a single WriteBatch - * is 500, but note that each usage of `FieldValue.serverTimestamp()`, - * `FieldValue.arrayUnion()`, `FieldValue.arrayRemove()`, or - * `FieldValue.increment()` inside a WriteBatch counts as an additional write. - * - * @return - * A `WriteBatch` that can be used to atomically execute multiple writes. - */ - batch(): WriteBatch; - - /** - * The {@link firebase.app.App app} associated with this `Firestore` service - * instance. - */ - app: firebase.app.App; - - /** - * Clears the persistent storage. This includes pending writes and cached - * documents. - * - * Must be called while the firestore instance is not started (after the app - * is shutdown or when the app is first initialized). On startup, this - * method must be called before other methods (other than settings()). If - * the firestore instance is still running, the promise will be rejected - * with the error code of `failed-precondition`. - * - * Note: clearPersistence() is primarily intended to help write reliable - * tests that use Cloud Firestore. It uses an efficient mechanism for - * dropping existing data but does not attempt to securely overwrite or - * otherwise make cached data unrecoverable. For applications that are - * sensitive to the disclosure of cached data in between user sessions, we - * strongly recommend not enabling persistence at all. - * - * @return A promise that is resolved when the persistent storage is - * cleared. Otherwise, the promise is rejected with an error. - */ - clearPersistence(): Promise; - - /** - * Re-enables use of the network for this Firestore instance after a prior - * call to {@link firebase.firestore.Firestore.disableNetwork - * `disableNetwork()`}. - * - * @return A promise that is resolved once the network has been - * enabled. - */ - enableNetwork(): Promise; - - /** - * Disables network usage for this instance. It can be re-enabled via - * {@link firebase.firestore.Firestore.enableNetwork `enableNetwork()`}. While - * the network is disabled, any snapshot listeners or get() calls will return - * results from cache, and any write operations will be queued until the network - * is restored. - * - * @return A promise that is resolved once the network has been - * disabled. - */ - disableNetwork(): Promise; - - /** - * Waits until all currently pending writes for the active user have been acknowledged by the - * backend. - * - * The returned Promise resolves immediately if there are no outstanding writes. Otherwise, the - * Promise waits for all previously issued writes (including those written in a previous app - * session), but it does not wait for writes that were added after the method is called. If you - * want to wait for additional writes, call `waitForPendingWrites()` again. - * - * Any outstanding `waitForPendingWrites()` Promises are rejected during user changes. - * - * @return A Promise which resolves when all currently pending writes have been - * acknowledged by the backend. - */ - waitForPendingWrites(): Promise; - - /** - * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync - * event indicates that all listeners affected by a given change have fired, - * even if a single server-generated change affects multiple listeners. - * - * NOTE: The snapshots-in-sync event only indicates that listeners are in sync - * with each other, but does not relate to whether those snapshots are in sync - * with the server. Use SnapshotMetadata in the individual listeners to - * determine if a snapshot is from the cache or the server. - * - * @param observer A single object containing `next` and `error` callbacks. - * @return An unsubscribe function that can be called to cancel the snapshot - * listener. - */ - onSnapshotsInSync(observer: { - next?: (value: void) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - }): () => void; - - /** - * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync - * event indicates that all listeners affected by a given change have fired, - * even if a single server-generated change affects multiple listeners. - * - * NOTE: The snapshots-in-sync event only indicates that listeners are in sync - * with each other, but does not relate to whether those snapshots are in sync - * with the server. Use SnapshotMetadata in the individual listeners to - * determine if a snapshot is from the cache or the server. - * - * @param onSync A callback to be called every time all snapshot listeners are - * in sync with each other. - * @return An unsubscribe function that can be called to cancel the snapshot - * listener. - */ - onSnapshotsInSync(onSync: () => void): () => void; - - /** - * Terminates this Firestore instance. - * - * After calling `terminate()` only the `clearPersistence()` method may be used. Any other method - * will throw a `FirestoreError`. - * - * To restart after termination, create a new instance of FirebaseFirestore with - * `firebase.firestore()`. - * - * Termination does not cancel any pending writes, and any promises that are awaiting a response - * from the server will not be resolved. If you have persistence enabled, the next time you - * start this instance, it will resume sending these writes to the server. - * - * Note: Under normal circumstances, calling `terminate()` is not required. This - * method is useful only when you want to force this instance to release all of its resources or - * in combination with `clearPersistence()` to ensure that all local state is destroyed - * between test runs. - * - * @return A promise that is resolved when the instance has been successfully terminated. - */ - terminate(): Promise; - - /** - * Loads a Firestore bundle into the local cache. - * - * @param bundleData - * An object representing the bundle to be loaded. Valid objects are `ArrayBuffer`, - * `ReadableStream` or `string`. - * - * @return - * A `LoadBundleTask` object, which notifies callers with progress updates, and completion - * or error events. It can be used as a `Promise`. - */ - loadBundle( - bundleData: ArrayBuffer | ReadableStream | string - ): LoadBundleTask; - - /** - * Reads a Firestore `Query` from local cache, identified by the given name. - * - * The named queries are packaged into bundles on the server side (along - * with resulting documents), and loaded to local cache using `loadBundle`. Once in local - * cache, use this method to extract a `Query` by name. - */ - namedQuery(name: string): Promise | null>; - - /** - * @hidden - */ - INTERNAL: { delete: () => Promise }; - } - - /** - * Represents the task of loading a Firestore bundle. It provides progress of bundle - * loading, as well as task completion and error events. - * - * The API is compatible with `Promise`. - */ - export interface LoadBundleTask extends PromiseLike { - /** - * Registers functions to listen to bundle loading progress events. - * @param next - * Called when there is a progress update from bundle loading. Typically `next` calls occur - * each time a Firestore document is loaded from the bundle. - * @param error - * Called when an error occurs during bundle loading. The task aborts after reporting the - * error, and there should be no more updates after this. - * @param complete - * Called when the loading task is complete. - */ - onProgress( - next?: (progress: LoadBundleTaskProgress) => any, - error?: (error: Error) => any, - complete?: () => void - ): void; - - /** - * Implements the `Promise.then` interface. - * - * @param onFulfilled - * Called on the completion of the loading task with a final `LoadBundleTaskProgress` update. - * The update will always have its `taskState` set to `"Success"`. - * @param onRejected - * Called when an error occurs during bundle loading. - */ - then( - onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, - onRejected?: (a: Error) => R | PromiseLike - ): Promise; - - /** - * Implements the `Promise.catch` interface. - * - * @param onRejected - * Called when an error occurs during bundle loading. - */ - catch( - onRejected: (a: Error) => R | PromiseLike - ): Promise; - } - - /** - * Represents a progress update or a final state from loading bundles. - */ - export interface LoadBundleTaskProgress { - /** How many documents have been loaded. */ - documentsLoaded: number; - /** How many documents are in the bundle being loaded. */ - totalDocuments: number; - /** How many bytes have been loaded. */ - bytesLoaded: number; - /** How many bytes are in the bundle being loaded. */ - totalBytes: number; - /** Current task state. */ - taskState: TaskState; - } - - /** - * Represents the state of bundle loading tasks. - * - * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will - * be no more updates after they are reported. - */ - export type TaskState = 'Error' | 'Running' | 'Success'; - - /** - * An immutable object representing a geo point in Firestore. The geo point - * is represented as latitude/longitude pair. - * - * Latitude values are in the range of [-90, 90]. - * Longitude values are in the range of [-180, 180]. - */ - export class GeoPoint { - /** - * Creates a new immutable GeoPoint object with the provided latitude and - * longitude values. - * @param latitude The latitude as number between -90 and 90. - * @param longitude The longitude as number between -180 and 180. - */ - constructor(latitude: number, longitude: number); - - /** - * The latitude of this GeoPoint instance. - */ - readonly latitude: number; - /** - * The longitude of this GeoPoint instance. - */ - readonly longitude: number; - - /** - * Returns true if this `GeoPoint` is equal to the provided one. - * - * @param other The `GeoPoint` to compare against. - * @return true if this `GeoPoint` is equal to the provided one. - */ - isEqual(other: GeoPoint): boolean; - } - - /** - * A Timestamp represents a point in time independent of any time zone or - * calendar, represented as seconds and fractions of seconds at nanosecond - * resolution in UTC Epoch time. - * - * It is encoded using the Proleptic Gregorian - * Calendar which extends the Gregorian calendar backwards to year one. It is - * encoded assuming all minutes are 60 seconds long, i.e. leap seconds are - * "smeared" so that no leap second table is needed for interpretation. Range is - * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. - * - * @see https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto - */ - export class Timestamp { - /** - * Creates a new timestamp. - * - * @param seconds The number of seconds of UTC time since Unix epoch - * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to - * 9999-12-31T23:59:59Z inclusive. - * @param nanoseconds The non-negative fractions of a second at nanosecond - * resolution. Negative second values with fractions must still have - * non-negative nanoseconds values that count forward in time. Must be - * from 0 to 999,999,999 inclusive. - */ - constructor(seconds: number, nanoseconds: number); - - /** - * Creates a new timestamp with the current date, with millisecond precision. - * - * @return a new timestamp representing the current date. - */ - static now(): Timestamp; - - /** - * Creates a new timestamp from the given date. - * - * @param date The date to initialize the `Timestamp` from. - * @return A new `Timestamp` representing the same point in time as the given - * date. - */ - static fromDate(date: Date): Timestamp; - - /** - * Creates a new timestamp from the given number of milliseconds. - * - * @param milliseconds Number of milliseconds since Unix epoch - * 1970-01-01T00:00:00Z. - * @return A new `Timestamp` representing the same point in time as the given - * number of milliseconds. - */ - static fromMillis(milliseconds: number): Timestamp; - - readonly seconds: number; - readonly nanoseconds: number; - - /** - * Convert a Timestamp to a JavaScript `Date` object. This conversion causes - * a loss of precision since `Date` objects only support millisecond precision. - * - * @return JavaScript `Date` object representing the same point in time as - * this `Timestamp`, with millisecond precision. - */ - toDate(): Date; - - /** - * Convert a timestamp to a numeric timestamp (in milliseconds since epoch). - * This operation causes a loss of precision. - * - * @return The point in time corresponding to this timestamp, represented as - * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. - */ - toMillis(): number; - - /** - * Returns true if this `Timestamp` is equal to the provided one. - * - * @param other The `Timestamp` to compare against. - * @return true if this `Timestamp` is equal to the provided one. - */ - isEqual(other: Timestamp): boolean; - - /** - * Converts this object to a primitive string, which allows Timestamp objects to be compared - * using the `>`, `<=`, `>=` and `>` operators. - */ - valueOf(): string; - } - - /** - * An immutable object representing an array of bytes. - */ - export class Blob { - private constructor(); - - /** - * Creates a new Blob from the given Base64 string, converting it to - * bytes. - * - * @param base64 - * The Base64 string used to create the Blob object. - */ - static fromBase64String(base64: string): Blob; - - /** - * Creates a new Blob from the given Uint8Array. - * - * @param array - * The Uint8Array used to create the Blob object. - */ - static fromUint8Array(array: Uint8Array): Blob; - - /** - * Returns the bytes of a Blob as a Base64-encoded string. - * - * @return - * The Base64-encoded string created from the Blob object. - */ - public toBase64(): string; - - /** - * Returns the bytes of a Blob in a new Uint8Array. - * - * @return - * The Uint8Array created from the Blob object. - */ - public toUint8Array(): Uint8Array; - - /** - * Returns true if this `Blob` is equal to the provided one. - * - * @param other The `Blob` to compare against. - * @return true if this `Blob` is equal to the provided one. - */ - isEqual(other: Blob): boolean; - } - - /** - * A reference to a transaction. - * The `Transaction` object passed to a transaction's updateFunction provides - * the methods to read and write data within the transaction context. See - * `Firestore.runTransaction()`. - */ - export class Transaction { - private constructor(); - - /** - * Reads the document referenced by the provided `DocumentReference.` - * - * @param documentRef A reference to the document to be read. - * @return A DocumentSnapshot for the read data. - */ - get(documentRef: DocumentReference): Promise>; - - /** - * Writes to the document referred to by the provided `DocumentReference`. - * If the document does not exist yet, it will be created. If you pass - * `SetOptions`, the provided data can be merged into the existing document. - * - * @param documentRef A reference to the document to be set. - * @param data An object of the fields and values for the document. - * @param options An object to configure the set behavior. - * @return This `Transaction` instance. Used for chaining method calls. - */ - set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): Transaction; - - /** - * Writes to the document referred to by the provided `DocumentReference`. - * If the document does not exist yet, it will be created. If you pass - * `SetOptions`, the provided data can be merged into the existing document. - * - * @param documentRef A reference to the document to be set. - * @param data An object of the fields and values for the document. - * @return This `Transaction` instance. Used for chaining method calls. - */ - set(documentRef: DocumentReference, data: T): Transaction; - - /** - * Updates fields in the document referred to by the provided - * `DocumentReference`. The update will fail if applied to a document that - * does not exist. - * - * @param documentRef A reference to the document to be updated. - * @param data An object containing the fields and values with which to - * update the document. Fields can contain dots to reference nested fields - * within the document. - * @return This `Transaction` instance. Used for chaining method calls. - */ - update(documentRef: DocumentReference, data: UpdateData): Transaction; - - /** - * Updates fields in the document referred to by the provided - * `DocumentReference`. The update will fail if applied to a document that - * does not exist. - * - * Nested fields can be updated by providing dot-separated field path - * strings or by providing FieldPath objects. - * - * @param documentRef A reference to the document to be updated. - * @param field The first field to update. - * @param value The first value. - * @param moreFieldsAndValues Additional key/value pairs. - * @return A Promise resolved once the data has been successfully written - * to the backend (Note that it won't resolve while you're offline). - */ - update( - documentRef: DocumentReference, - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] - ): Transaction; - - /** - * Deletes the document referred to by the provided `DocumentReference`. - * - * @param documentRef A reference to the document to be deleted. - * @return This `Transaction` instance. Used for chaining method calls. - */ - delete(documentRef: DocumentReference): Transaction; - } - - /** - * A write batch, used to perform multiple writes as a single atomic unit. - * - * A `WriteBatch` object can be acquired by calling `Firestore.batch()`. It - * provides methods for adding writes to the write batch. None of the - * writes will be committed (or visible locally) until `WriteBatch.commit()` - * is called. - * - * Unlike transactions, write batches are persisted offline and therefore are - * preferable when you don't need to condition your writes on read data. - */ - export class WriteBatch { - private constructor(); - - /** - * Writes to the document referred to by the provided `DocumentReference`. - * If the document does not exist yet, it will be created. If you pass - * `SetOptions`, the provided data can be merged into the existing document. - * - * @param documentRef A reference to the document to be set. - * @param data An object of the fields and values for the document. - * @param options An object to configure the set behavior. - * @return This `WriteBatch` instance. Used for chaining method calls. - */ - set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): WriteBatch; - - /** - * Writes to the document referred to by the provided `DocumentReference`. - * If the document does not exist yet, it will be created. If you pass - * `SetOptions`, the provided data can be merged into the existing document. - * - * @param documentRef A reference to the document to be set. - * @param data An object of the fields and values for the document. - * @return This `WriteBatch` instance. Used for chaining method calls. - */ - set(documentRef: DocumentReference, data: T): WriteBatch; - - /** - * Updates fields in the document referred to by the provided - * `DocumentReference`. The update will fail if applied to a document that - * does not exist. - * - * @param documentRef A reference to the document to be updated. - * @param data An object containing the fields and values with which to - * update the document. Fields can contain dots to reference nested fields - * within the document. - * @return This `WriteBatch` instance. Used for chaining method calls. - */ - update(documentRef: DocumentReference, data: UpdateData): WriteBatch; - - /** - * Updates fields in the document referred to by this `DocumentReference`. - * The update will fail if applied to a document that does not exist. - * - * Nested fields can be update by providing dot-separated field path strings - * or by providing FieldPath objects. - * - * @param documentRef A reference to the document to be updated. - * @param field The first field to update. - * @param value The first value. - * @param moreFieldsAndValues Additional key value pairs. - * @return A Promise resolved once the data has been successfully written - * to the backend (Note that it won't resolve while you're offline). - */ - update( - documentRef: DocumentReference, - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] - ): WriteBatch; - - /** - * Deletes the document referred to by the provided `DocumentReference`. - * - * @param documentRef A reference to the document to be deleted. - * @return This `WriteBatch` instance. Used for chaining method calls. - */ - delete(documentRef: DocumentReference): WriteBatch; - - /** - * Commits all of the writes in this write batch as a single atomic unit. - * - * @return A Promise resolved once all of the writes in the batch have been - * successfully written to the backend as an atomic unit. Note that it won't - * resolve while you're offline. - */ - commit(): Promise; - } - - /** - * An options object that can be passed to `DocumentReference.onSnapshot()`, - * `Query.onSnapshot()` and `QuerySnapshot.docChanges()` to control which - * types of changes to include in the result set. - */ - export interface SnapshotListenOptions { - /** - * Include a change even if only the metadata of the query or of a document - * changed. Default is false. - */ - readonly includeMetadataChanges?: boolean; - } - - /** - * An options object that configures the behavior of `set()` calls in - * {@link firebase.firestore.DocumentReference.set DocumentReference}, {@link - * firebase.firestore.WriteBatch.set WriteBatch} and {@link - * firebase.firestore.Transaction.set Transaction}. These calls can be - * configured to perform granular merges instead of overwriting the target - * documents in their entirety by providing a `SetOptions` with `merge: true`. - */ - export interface SetOptions { - /** - * Changes the behavior of a set() call to only replace the values specified - * in its data argument. Fields omitted from the set() call remain - * untouched. - */ - readonly merge?: boolean; - - /** - * Changes the behavior of set() calls to only replace the specified field - * paths. Any field path that is not specified is ignored and remains - * untouched. - */ - readonly mergeFields?: (string | FieldPath)[]; - } - - /** - * An options object that configures the behavior of `get()` calls on - * `DocumentReference` and `Query`. By providing a `GetOptions` object, these - * methods can be configured to fetch results only from the server, only from - * the local cache or attempt to fetch results from the server and fall back to - * the cache (which is the default). - */ - export interface GetOptions { - /** - * Describes whether we should get from server or cache. - * - * Setting to `default` (or not setting at all), causes Firestore to try to - * retrieve an up-to-date (server-retrieved) snapshot, but fall back to - * returning cached data if the server can't be reached. - * - * Setting to `server` causes Firestore to avoid the cache, generating an - * error if the server cannot be reached. Note that the cache will still be - * updated if the server request succeeds. Also note that latency-compensation - * still takes effect, so any pending write operations will be visible in the - * returned data (merged into the server-provided data). - * - * Setting to `cache` causes Firestore to immediately return a value from the - * cache, ignoring the server completely (implying that the returned value - * may be stale with respect to the value on the server.) If there is no data - * in the cache to satisfy the `get()` call, `DocumentReference.get()` will - * return an error and `QuerySnapshot.get()` will return an empty - * `QuerySnapshot` with no documents. - */ - readonly source?: 'default' | 'server' | 'cache'; - } - - /** - * A `DocumentReference` refers to a document location in a Firestore database - * and can be used to write, read, or listen to the location. The document at - * the referenced location may or may not exist. A `DocumentReference` can - * also be used to create a `CollectionReference` to a subcollection. - */ - export class DocumentReference { - private constructor(); - - /** - * The document's identifier within its collection. - */ - readonly id: string; - - /** - * The {@link firebase.firestore.Firestore} the document is in. - * This is useful for performing transactions, for example. - */ - readonly firestore: Firestore; - - /** - * The Collection this `DocumentReference` belongs to. - */ - readonly parent: CollectionReference; - - /** - * A string representing the path of the referenced document (relative - * to the root of the database). - */ - readonly path: string; - - /** - * Gets a `CollectionReference` instance that refers to the collection at - * the specified path. - * - * @param collectionPath A slash-separated path to a collection. - * @return The `CollectionReference` instance. - */ - collection(collectionPath: string): CollectionReference; - - /** - * Returns true if this `DocumentReference` is equal to the provided one. - * - * @param other The `DocumentReference` to compare against. - * @return true if this `DocumentReference` is equal to the provided one. - */ - isEqual(other: DocumentReference): boolean; - - /** - * Writes to the document referred to by this `DocumentReference`. If the - * document does not yet exist, it will be created. If you pass - * `SetOptions`, the provided data can be merged into an existing document. - * - * @param data A map of the fields and values for the document. - * @param options An object to configure the set behavior. - * @return A Promise resolved once the data has been successfully written - * to the backend (Note that it won't resolve while you're offline). - */ - set(data: Partial, options: SetOptions): Promise; - - /** - * Writes to the document referred to by this `DocumentReference`. If the - * document does not yet exist, it will be created. If you pass - * `SetOptions`, the provided data can be merged into an existing document. - * - * @param data A map of the fields and values for the document. - * @return A Promise resolved once the data has been successfully written - * to the backend (Note that it won't resolve while you're offline). - */ - set(data: T): Promise; - - /** - * Updates fields in the document referred to by this `DocumentReference`. - * The update will fail if applied to a document that does not exist. - * - * @param data An object containing the fields and values with which to - * update the document. Fields can contain dots to reference nested fields - * within the document. - * @return A Promise resolved once the data has been successfully written - * to the backend (Note that it won't resolve while you're offline). - */ - update(data: UpdateData): Promise; - - /** - * Updates fields in the document referred to by this `DocumentReference`. - * The update will fail if applied to a document that does not exist. - * - * Nested fields can be updated by providing dot-separated field path - * strings or by providing FieldPath objects. - * - * @param field The first field to update. - * @param value The first value. - * @param moreFieldsAndValues Additional key value pairs. - * @return A Promise resolved once the data has been successfully written - * to the backend (Note that it won't resolve while you're offline). - */ - update( - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] - ): Promise; - - /** - * Deletes the document referred to by this `DocumentReference`. - * - * @return A Promise resolved once the document has been successfully - * deleted from the backend (Note that it won't resolve while you're - * offline). - */ - delete(): Promise; - - /** - * Reads the document referred to by this `DocumentReference`. - * - * Note: By default, get() attempts to provide up-to-date data when possible - * by waiting for data from the server, but it may return cached data or fail - * if you are offline and the server cannot be reached. This behavior can be - * altered via the `GetOptions` parameter. - * - * @param options An object to configure the get behavior. - * @return A Promise resolved with a DocumentSnapshot containing the - * current document contents. - */ - get(options?: GetOptions): Promise>; - - /** - * Attaches a listener for DocumentSnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param observer A single object containing `next` and `error` callbacks. - * @return An unsubscribe function that can be called to cancel - * the snapshot listener. - */ - onSnapshot(observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - }): () => void; - /** - * Attaches a listener for DocumentSnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param options Options controlling the listen behavior. - * @param observer A single object containing `next` and `error` callbacks. - * @return An unsubscribe function that can be called to cancel - * the snapshot listener. - */ - onSnapshot( - options: SnapshotListenOptions, - observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } - ): () => void; - /** - * Attaches a listener for DocumentSnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param onNext A callback to be called every time a new `DocumentSnapshot` - * is available. - * @param onError A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @return An unsubscribe function that can be called to cancel - * the snapshot listener. - */ - onSnapshot( - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void - ): () => void; - /** - * Attaches a listener for DocumentSnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param options Options controlling the listen behavior. - * @param onNext A callback to be called every time a new `DocumentSnapshot` - * is available. - * @param onError A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @return An unsubscribe function that can be called to cancel - * the snapshot listener. - */ - onSnapshot( - options: SnapshotListenOptions, - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void - ): () => void; - - /** - * Applies a custom data converter to this DocumentReference, allowing you - * to use your own custom model objects with Firestore. When you call - * set(), get(), etc. on the returned DocumentReference instance, the - * provided converter will convert between Firestore data and your custom - * type U. - * - * Passing in `null` as the converter parameter removes the current - * converter. - * - * @param converter Converts objects to and from Firestore. Passing in - * `null` removes the current converter. - * @return A DocumentReference that uses the provided converter. - */ - withConverter(converter: null): DocumentReference; - /** - * Applies a custom data converter to this DocumentReference, allowing you - * to use your own custom model objects with Firestore. When you call - * set(), get(), etc. on the returned DocumentReference instance, the - * provided converter will convert between Firestore data and your custom - * type U. - * - * Passing in `null` as the converter parameter removes the current - * converter. - * - * @param converter Converts objects to and from Firestore. Passing in - * `null` removes the current converter. - * @return A DocumentReference that uses the provided converter. - */ - withConverter( - converter: FirestoreDataConverter - ): DocumentReference; - } - - /** - * Options that configure how data is retrieved from a `DocumentSnapshot` - * (e.g. the desired behavior for server timestamps that have not yet been set - * to their final value). - */ - export interface SnapshotOptions { - /** - * If set, controls the return value for server timestamps that have not yet - * been set to their final value. - * - * By specifying 'estimate', pending server timestamps return an estimate - * based on the local clock. This estimate will differ from the final value - * and cause these values to change once the server result becomes available. - * - * By specifying 'previous', pending timestamps will be ignored and return - * their previous value instead. - * - * If omitted or set to 'none', `null` will be returned by default until the - * server value becomes available. - */ - readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; - } - - /** - * Metadata about a snapshot, describing the state of the snapshot. - */ - export interface SnapshotMetadata { - /** - * True if the snapshot contains the result of local writes (e.g. set() or - * update() calls) that have not yet been committed to the backend. - * If your listener has opted into metadata updates (via - * `SnapshotListenOptions`) you will receive another - * snapshot with `hasPendingWrites` equal to false once the writes have been - * committed to the backend. - */ - readonly hasPendingWrites: boolean; - - /** - * True if the snapshot was created from cached data rather than guaranteed - * up-to-date server data. If your listener has opted into metadata updates - * (via `SnapshotListenOptions`) - * you will receive another snapshot with `fromCache` set to false once - * the client has received up-to-date data from the backend. - */ - readonly fromCache: boolean; - - /** - * Returns true if this `SnapshotMetadata` is equal to the provided one. - * - * @param other The `SnapshotMetadata` to compare against. - * @return true if this `SnapshotMetadata` is equal to the provided one. - */ - isEqual(other: SnapshotMetadata): boolean; - } - - /** - * A `DocumentSnapshot` contains data read from a document in your Firestore - * database. The data can be extracted with `.data()` or `.get()` to - * get a specific field. - * - * For a `DocumentSnapshot` that points to a non-existing document, any data - * access will return 'undefined'. You can use the `exists` property to - * explicitly verify a document's existence. - */ - export class DocumentSnapshot { - protected constructor(); - - /** - * Property of the `DocumentSnapshot` that signals whether or not the data - * exists. True if the document exists. - */ - readonly exists: boolean; - /** - * The `DocumentReference` for the document included in the `DocumentSnapshot`. - */ - readonly ref: DocumentReference; - /** - * Property of the `DocumentSnapshot` that provides the document's ID. - */ - readonly id: string; - /** - * Metadata about the `DocumentSnapshot`, including information about its - * source and local modifications. - */ - readonly metadata: SnapshotMetadata; - - /** - * Retrieves all fields in the document as an Object. Returns 'undefined' if - * the document doesn't exist. - * - * By default, `FieldValue.serverTimestamp()` values that have not yet been - * set to their final value will be returned as `null`. You can override - * this by passing an options object. - * - * @param options An options object to configure how data is retrieved from - * the snapshot (e.g. the desired behavior for server timestamps that have - * not yet been set to their final value). - * @return An Object containing all fields in the document or 'undefined' if - * the document doesn't exist. - */ - data(options?: SnapshotOptions): T | undefined; - - /** - * Retrieves the field specified by `fieldPath`. Returns `undefined` if the - * document or field doesn't exist. - * - * By default, a `FieldValue.serverTimestamp()` that has not yet been set to - * its final value will be returned as `null`. You can override this by - * passing an options object. - * - * @param fieldPath The path (e.g. 'foo' or 'foo.bar') to a specific field. - * @param options An options object to configure how the field is retrieved - * from the snapshot (e.g. the desired behavior for server timestamps that have - * not yet been set to their final value). - * @return The data at the specified field location or undefined if no such - * field exists in the document. - */ - get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; - - /** - * Returns true if this `DocumentSnapshot` is equal to the provided one. - * - * @param other The `DocumentSnapshot` to compare against. - * @return true if this `DocumentSnapshot` is equal to the provided one. - */ - isEqual(other: DocumentSnapshot): boolean; - } - - /** - * A `QueryDocumentSnapshot` contains data read from a document in your - * Firestore database as part of a query. The document is guaranteed to exist - * and its data can be extracted with `.data()` or `.get()` to get a - * specific field. - * - * A `QueryDocumentSnapshot` offers the same API surface as a - * `DocumentSnapshot`. Since query results contain only existing documents, the - * `exists` property will always be true and `data()` will never return - * 'undefined'. - */ - export class QueryDocumentSnapshot< - T = DocumentData - > extends DocumentSnapshot { - private constructor(); - - /** - * Retrieves all fields in the document as an Object. - * - * By default, `FieldValue.serverTimestamp()` values that have not yet been - * set to their final value will be returned as `null`. You can override - * this by passing an options object. - * - * @override - * @param options An options object to configure how data is retrieved from - * the snapshot (e.g. the desired behavior for server timestamps that have - * not yet been set to their final value). - * @return An Object containing all fields in the document. - */ - data(options?: SnapshotOptions): T; - } - - /** - * The direction of a `Query.orderBy()` clause is specified as 'desc' or 'asc' - * (descending or ascending). - */ - export type OrderByDirection = 'desc' | 'asc'; - - /** - * Filter conditions in a `Query.where()` clause are specified using the - * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', - * 'array-contains-any', and 'not-in'. - */ - export type WhereFilterOp = - | '<' - | '<=' - | '==' - | '!=' - | '>=' - | '>' - | 'array-contains' - | 'in' - | 'array-contains-any' - | 'not-in'; - - /** - * A `Query` refers to a Query which you can read or listen to. You can also - * construct refined `Query` objects by adding filters and ordering. - */ - export class Query { - protected constructor(); - - /** - * The `Firestore` for the Firestore database (useful for performing - * transactions, etc.). - */ - readonly firestore: Firestore; - - /** - * Creates and returns a new Query with the additional filter that documents - * must contain the specified field and the value should satisfy the - * relation constraint provided. - * - * @param fieldPath The path to compare - * @param opStr The operation string (e.g "<", "<=", "==", ">", ">="). - * @param value The value for comparison - * @return The created Query. - */ - where( - fieldPath: string | FieldPath, - opStr: WhereFilterOp, - value: any - ): Query; - - /** - * Creates and returns a new Query that's additionally sorted by the - * specified field, optionally in descending order instead of ascending. - * - * @param fieldPath The field to sort by. - * @param directionStr Optional direction to sort by (`asc` or `desc`). If - * not specified, order will be ascending. - * @return The created Query. - */ - orderBy( - fieldPath: string | FieldPath, - directionStr?: OrderByDirection - ): Query; - - /** - * Creates and returns a new Query that only returns the first matching - * documents. - * - * @param limit The maximum number of items to return. - * @return The created Query. - */ - limit(limit: number): Query; - - /** - * Creates and returns a new Query that only returns the last matching - * documents. - * - * You must specify at least one `orderBy` clause for `limitToLast` queries, - * otherwise an exception will be thrown during execution. - * - * @param limit The maximum number of items to return. - * @return The created Query. - */ - limitToLast(limit: number): Query; - - /** - * Creates and returns a new Query that starts at the provided document - * (inclusive). The starting position is relative to the order of the query. - * The document must contain all of the fields provided in the `orderBy` of - * this query. - * - * @param snapshot The snapshot of the document to start at. - * @return The created Query. - */ - startAt(snapshot: DocumentSnapshot): Query; - - /** - * Creates and returns a new Query that starts at the provided fields - * relative to the order of the query. The order of the field values - * must match the order of the order by clauses of the query. - * - * @param fieldValues The field values to start this query at, in order - * of the query's order by. - * @return The created Query. - */ - startAt(...fieldValues: any[]): Query; - - /** - * Creates and returns a new Query that starts after the provided document - * (exclusive). The starting position is relative to the order of the query. - * The document must contain all of the fields provided in the orderBy of - * this query. - * - * @param snapshot The snapshot of the document to start after. - * @return The created Query. - */ - startAfter(snapshot: DocumentSnapshot): Query; - - /** - * Creates and returns a new Query that starts after the provided fields - * relative to the order of the query. The order of the field values - * must match the order of the order by clauses of the query. - * - * @param fieldValues The field values to start this query after, in order - * of the query's order by. - * @return The created Query. - */ - startAfter(...fieldValues: any[]): Query; - - /** - * Creates and returns a new Query that ends before the provided document - * (exclusive). The end position is relative to the order of the query. The - * document must contain all of the fields provided in the orderBy of this - * query. - * - * @param snapshot The snapshot of the document to end before. - * @return The created Query. - */ - endBefore(snapshot: DocumentSnapshot): Query; - - /** - * Creates and returns a new Query that ends before the provided fields - * relative to the order of the query. The order of the field values - * must match the order of the order by clauses of the query. - * - * @param fieldValues The field values to end this query before, in order - * of the query's order by. - * @return The created Query. - */ - endBefore(...fieldValues: any[]): Query; - - /** - * Creates and returns a new Query that ends at the provided document - * (inclusive). The end position is relative to the order of the query. The - * document must contain all of the fields provided in the orderBy of this - * query. - * - * @param snapshot The snapshot of the document to end at. - * @return The created Query. - */ - endAt(snapshot: DocumentSnapshot): Query; - - /** - * Creates and returns a new Query that ends at the provided fields - * relative to the order of the query. The order of the field values - * must match the order of the order by clauses of the query. - * - * @param fieldValues The field values to end this query at, in order - * of the query's order by. - * @return The created Query. - */ - endAt(...fieldValues: any[]): Query; - - /** - * Returns true if this `Query` is equal to the provided one. - * - * @param other The `Query` to compare against. - * @return true if this `Query` is equal to the provided one. - */ - isEqual(other: Query): boolean; - - /** - * Executes the query and returns the results as a `QuerySnapshot`. - * - * Note: By default, get() attempts to provide up-to-date data when possible - * by waiting for data from the server, but it may return cached data or fail - * if you are offline and the server cannot be reached. This behavior can be - * altered via the `GetOptions` parameter. - * - * @param options An object to configure the get behavior. - * @return A Promise that will be resolved with the results of the Query. - */ - get(options?: GetOptions): Promise>; - - /** - * Attaches a listener for QuerySnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param observer A single object containing `next` and `error` callbacks. - * @return An unsubscribe function that can be called to cancel - * the snapshot listener. - */ - onSnapshot(observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - }): () => void; - /** - * Attaches a listener for QuerySnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param options Options controlling the listen behavior. - * @param observer A single object containing `next` and `error` callbacks. - * @return An unsubscribe function that can be called to cancel - * the snapshot listener. - */ - onSnapshot( - options: SnapshotListenOptions, - observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } - ): () => void; - /** - * Attaches a listener for QuerySnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param onNext A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onError A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @return An unsubscribe function that can be called to cancel - * the snapshot listener. - */ - onSnapshot( - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void - ): () => void; - /** - * Attaches a listener for QuerySnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param options Options controlling the listen behavior. - * @param onNext A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onError A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @return An unsubscribe function that can be called to cancel - * the snapshot listener. - */ - onSnapshot( - options: SnapshotListenOptions, - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void - ): () => void; - - /** - * Applies a custom data converter to this Query, allowing you to use your - * own custom model objects with Firestore. When you call get() on the - * returned Query, the provided converter will convert between Firestore - * data and your custom type U. - * - * Passing in `null` as the converter parameter removes the current - * converter. - * - * @param converter Converts objects to and from Firestore. Passing in - * `null` removes the current converter. - * @return A Query that uses the provided converter. - */ - withConverter(converter: null): Query; - /** - * Applies a custom data converter to this Query, allowing you to use your - * own custom model objects with Firestore. When you call get() on the - * returned Query, the provided converter will convert between Firestore - * data and your custom type U. - * - * Passing in `null` as the converter parameter removes the current - * converter. - * - * @param converter Converts objects to and from Firestore. Passing in - * `null` removes the current converter. - * @return A Query that uses the provided converter. - */ - withConverter(converter: FirestoreDataConverter): Query; - } - - /** - * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects - * representing the results of a query. The documents can be accessed as an - * array via the `docs` property or enumerated using the `forEach` method. The - * number of documents can be determined via the `empty` and `size` - * properties. - */ - export class QuerySnapshot { - private constructor(); - - /** - * The query on which you called `get` or `onSnapshot` in order to get this - * `QuerySnapshot`. - */ - readonly query: Query; - /** - * Metadata about this snapshot, concerning its source and if it has local - * modifications. - */ - readonly metadata: SnapshotMetadata; - - /** An array of all the documents in the `QuerySnapshot`. */ - readonly docs: Array>; - - /** The number of documents in the `QuerySnapshot`. */ - readonly size: number; - - /** True if there are no documents in the `QuerySnapshot`. */ - readonly empty: boolean; - - /** - * Returns an array of the documents changes since the last snapshot. If this - * is the first snapshot, all documents will be in the list as added changes. - * - * @param options `SnapshotListenOptions` that control whether metadata-only - * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger - * snapshot events. - */ - docChanges(options?: SnapshotListenOptions): Array>; - - /** - * Enumerates all of the documents in the `QuerySnapshot`. - * - * @param callback A callback to be called with a `QueryDocumentSnapshot` for - * each document in the snapshot. - * @param thisArg The `this` binding for the callback. - */ - forEach( - callback: (result: QueryDocumentSnapshot) => void, - thisArg?: any - ): void; - - /** - * Returns true if this `QuerySnapshot` is equal to the provided one. - * - * @param other The `QuerySnapshot` to compare against. - * @return true if this `QuerySnapshot` is equal to the provided one. - */ - isEqual(other: QuerySnapshot): boolean; - } - - /** - * The type of a `DocumentChange` may be 'added', 'removed', or 'modified'. - */ - export type DocumentChangeType = 'added' | 'removed' | 'modified'; - - /** - * A `DocumentChange` represents a change to the documents matching a query. - * It contains the document affected and the type of change that occurred. - */ - export interface DocumentChange { - /** The type of change ('added', 'modified', or 'removed'). */ - readonly type: DocumentChangeType; - - /** The document affected by this change. */ - readonly doc: QueryDocumentSnapshot; - - /** - * The index of the changed document in the result set immediately prior to - * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects - * have been applied). Is -1 for 'added' events. - */ - readonly oldIndex: number; - - /** - * The index of the changed document in the result set immediately after - * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` - * objects and the current `DocumentChange` object have been applied). - * Is -1 for 'removed' events. - */ - readonly newIndex: number; - } - - /** - * A `CollectionReference` object can be used for adding documents, getting - * document references, and querying for documents (using the methods - * inherited from `Query`). - */ - export class CollectionReference extends Query { - private constructor(); - - /** The collection's identifier. */ - readonly id: string; - - /** - * A reference to the containing `DocumentReference` if this is a subcollection. - * If this isn't a subcollection, the reference is null. - */ - readonly parent: DocumentReference | null; - - /** - * A string representing the path of the referenced collection (relative - * to the root of the database). - */ - readonly path: string; - - /** - * Get a `DocumentReference` for the document within the collection at the - * specified path. If no path is specified, an automatically-generated - * unique ID will be used for the returned DocumentReference. - * - * @param documentPath A slash-separated path to a document. - * @return The `DocumentReference` instance. - */ - doc(documentPath?: string): DocumentReference; - - /** - * Add a new document to this collection with the specified data, assigning - * it a document ID automatically. - * - * @param data An Object containing the data for the new document. - * @return A Promise resolved with a `DocumentReference` pointing to the - * newly created document after it has been written to the backend. - */ - add(data: T): Promise>; - - /** - * Returns true if this `CollectionReference` is equal to the provided one. - * - * @param other The `CollectionReference` to compare against. - * @return true if this `CollectionReference` is equal to the provided one. - */ - isEqual(other: CollectionReference): boolean; - - /** - * Applies a custom data converter to this CollectionReference, allowing you - * to use your own custom model objects with Firestore. When you call add() - * on the returned CollectionReference instance, the provided converter will - * convert between Firestore data and your custom type U. - * - * Passing in `null` as the converter parameter removes the current - * converter. - * - * @param converter Converts objects to and from Firestore. Passing in - * `null` removes the current converter. - * @return A CollectionReference that uses the provided converter. - */ - withConverter(converter: null): CollectionReference; - /** - * Applies a custom data converter to this CollectionReference, allowing you - * to use your own custom model objects with Firestore. When you call add() - * on the returned CollectionReference instance, the provided converter will - * convert between Firestore data and your custom type U. - * - * Passing in `null` as the converter parameter removes the current - * converter. - * - * @param converter Converts objects to and from Firestore. Passing in - * `null` removes the current converter. - * @return A CollectionReference that uses the provided converter. - */ - withConverter( - converter: FirestoreDataConverter - ): CollectionReference; - } - - /** - * Sentinel values that can be used when writing document fields with `set()` - * or `update()`. - */ - export class FieldValue { - private constructor(); - - /** - * Returns a sentinel used with `set()` or `update()` to include a - * server-generated timestamp in the written data. - */ - static serverTimestamp(): FieldValue; - - /** - * Returns a sentinel for use with `update()` to mark a field for deletion. - */ - static delete(): FieldValue; - - /** - * Returns a special value that can be used with `set()` or `update()` that tells - * the server to union the given elements with any array value that already - * exists on the server. Each specified element that doesn't already exist in - * the array will be added to the end. If the field being modified is not - * already an array it will be overwritten with an array containing exactly - * the specified elements. - * - * @param elements The elements to union into the array. - * @return The FieldValue sentinel for use in a call to `set()` or `update()`. - */ - static arrayUnion(...elements: any[]): FieldValue; - - /** - * Returns a special value that can be used with `set()` or `update()` that tells - * the server to remove the given elements from any array value that already - * exists on the server. All instances of each element specified will be - * removed from the array. If the field being modified is not already an - * array it will be overwritten with an empty array. - * - * @param elements The elements to remove from the array. - * @return The FieldValue sentinel for use in a call to `set()` or `update()`. - */ - static arrayRemove(...elements: any[]): FieldValue; - - /** - * Returns a special value that can be used with `set()` or `update()` that tells - * the server to increment the field's current value by the given value. - * - * If either the operand or the current field value uses floating point precision, - * all arithmetic follows IEEE 754 semantics. If both values are integers, - * values outside of JavaScript's safe number range (`Number.MIN_SAFE_INTEGER` to - * `Number.MAX_SAFE_INTEGER`) are also subject to precision loss. Furthermore, - * once processed by the Firestore backend, all integer operations are capped - * between -2^63 and 2^63-1. - * - * If the current field value is not of type `number`, or if the field does not - * yet exist, the transformation sets the field to the given value. - * - * @param n The value to increment by. - * @return The FieldValue sentinel for use in a call to `set()` or `update()`. - */ - static increment(n: number): FieldValue; - - /** - * Returns true if this `FieldValue` is equal to the provided one. - * - * @param other The `FieldValue` to compare against. - * @return true if this `FieldValue` is equal to the provided one. - */ - isEqual(other: FieldValue): boolean; - } - - /** - * A FieldPath refers to a field in a document. The path may consist of a - * single field name (referring to a top-level field in the document), or a - * list of field names (referring to a nested field in the document). - * - * Create a FieldPath by providing field names. If more than one field - * name is provided, the path will point to a nested field in a document. - * - */ - export class FieldPath { - /** - * Creates a FieldPath from the provided field names. If more than one field - * name is provided, the path will point to a nested field in a document. - * - * @param fieldNames A list of field names. - */ - constructor(...fieldNames: string[]); - - /** - * Returns a special sentinel `FieldPath` to refer to the ID of a document. - * It can be used in queries to sort or filter by the document ID. - */ - static documentId(): FieldPath; - - /** - * Returns true if this `FieldPath` is equal to the provided one. - * - * @param other The `FieldPath` to compare against. - * @return true if this `FieldPath` is equal to the provided one. - */ - isEqual(other: FieldPath): boolean; - } - - /** - * The set of Firestore status codes. The codes are the same at the ones - * exposed by gRPC here: - * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - * - * Possible values: - * - 'cancelled': The operation was cancelled (typically by the caller). - * - 'unknown': Unknown error or an error from a different error domain. - * - 'invalid-argument': Client specified an invalid argument. Note that this - * differs from 'failed-precondition'. 'invalid-argument' indicates - * arguments that are problematic regardless of the state of the system - * (e.g. an invalid field name). - * - 'deadline-exceeded': Deadline expired before operation could complete. - * For operations that change the state of the system, this error may be - * returned even if the operation has completed successfully. For example, - * a successful response from a server could have been delayed long enough - * for the deadline to expire. - * - 'not-found': Some requested document was not found. - * - 'already-exists': Some document that we attempted to create already - * exists. - * - 'permission-denied': The caller does not have permission to execute the - * specified operation. - * - 'resource-exhausted': Some resource has been exhausted, perhaps a - * per-user quota, or perhaps the entire file system is out of space. - * - 'failed-precondition': Operation was rejected because the system is not - * in a state required for the operation's execution. - * - 'aborted': The operation was aborted, typically due to a concurrency - * issue like transaction aborts, etc. - * - 'out-of-range': Operation was attempted past the valid range. - * - 'unimplemented': Operation is not implemented or not supported/enabled. - * - 'internal': Internal errors. Means some invariants expected by - * underlying system has been broken. If you see one of these errors, - * something is very broken. - * - 'unavailable': The service is currently unavailable. This is most likely - * a transient condition and may be corrected by retrying with a backoff. - * - 'data-loss': Unrecoverable data loss or corruption. - * - 'unauthenticated': The request does not have valid authentication - * credentials for the operation. - */ - export type FirestoreErrorCode = - | 'cancelled' - | 'unknown' - | 'invalid-argument' - | 'deadline-exceeded' - | 'not-found' - | 'already-exists' - | 'permission-denied' - | 'resource-exhausted' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'unimplemented' - | 'internal' - | 'unavailable' - | 'data-loss' - | 'unauthenticated'; - - /** An error returned by a Firestore operation. */ - // TODO(b/63008957): FirestoreError should extend firebase.FirebaseError - export interface FirestoreError { - code: FirestoreErrorCode; - message: string; - name: string; - stack?: string; - } -} - -export default firebase; -export as namespace firebase; diff --git a/packages-exp/firebase-exp/compat/messaging/package.json b/packages-exp/firebase-exp/compat/messaging/package.json deleted file mode 100644 index 453a800ec4a..00000000000 --- a/packages-exp/firebase-exp/compat/messaging/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/messaging", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/messaging/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/performance/package.json b/packages-exp/firebase-exp/compat/performance/package.json deleted file mode 100644 index dc3b43f2221..00000000000 --- a/packages-exp/firebase-exp/compat/performance/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/performance", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/performance/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/remote-config/package.json b/packages-exp/firebase-exp/compat/remote-config/package.json deleted file mode 100644 index 9f68a1f4669..00000000000 --- a/packages-exp/firebase-exp/compat/remote-config/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/remote-config", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/remote-config/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/compat/rollup.config.release.js b/packages-exp/firebase-exp/compat/rollup.config.release.js deleted file mode 100644 index 0cc32372299..00000000000 --- a/packages-exp/firebase-exp/compat/rollup.config.release.js +++ /dev/null @@ -1,411 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { resolve } from 'path'; -import resolveModule from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import sourcemaps from 'rollup-plugin-sourcemaps'; -import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { terser } from 'rollup-plugin-terser'; -import json from '@rollup/plugin-json'; -import pkg from '../package.json'; -import compatPkg from './package.json'; -import appPkg from './app/package.json'; -import alias from '@rollup/plugin-alias'; - -const external = Object.keys(pkg.dependencies || {}); - -/** - * Global UMD Build - */ -const GLOBAL_NAME = 'firebase'; - -function createUmdOutputConfig(output) { - return { - file: output, - format: 'umd', - sourcemap: true, - extend: true, - name: GLOBAL_NAME, - globals: { - '@firebase/app-compat': GLOBAL_NAME, - '@firebase/app': `${GLOBAL_NAME}.INTERNAL.modularAPIs` - }, - - /** - * use iife to avoid below error in the old Safari browser - * SyntaxError: Functions cannot be declared in a nested block in strict mode - * https://github.com/firebase/firebase-js-sdk/issues/1228 - * - */ - intro: ` - try { - (function() {`, - outro: ` - }).apply(this, arguments); - } catch(err) { - console.error(err); - throw new Error( - 'Cannot instantiate ${output} - ' + - 'be sure to load firebase-app.js first.' - ); - }` - }; -} - -const plugins = [ - sourcemaps(), - resolveModule({ - // hack to find firestore-compat and storage-compat - moduleDirectories: ['node_modules', resolve(__dirname, '../..')] - }), - json(), - commonjs() -]; - -const typescriptPlugin = rollupTypescriptPlugin({ - typescript -}); - -const typescriptPluginUMD = rollupTypescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - declaration: false - } - } -}); - -/** - * Individual Component Builds - */ -const appBuilds = [ - /** - * App NPM Builds - */ - { - input: `${__dirname}/app/index.ts`, - output: [ - { - file: resolve(__dirname, 'app', appPkg.main), - format: 'cjs', - sourcemap: true - }, - { - file: resolve(__dirname, 'app', appPkg.module), - format: 'es', - sourcemap: true - } - ], - plugins: [...plugins, typescriptPlugin], - external - }, - /** - * App UMD Builds - */ - { - input: `${__dirname}/app/index.cdn.ts`, - output: { - file: 'firebase-app-compat.js', - sourcemap: true, - format: 'umd', - name: GLOBAL_NAME - }, - plugins: [ - ...plugins, - typescriptPluginUMD, - alias({ - entries: [ - { - find: '@firebase/app', - replacement: '@firebase/app-exp' - } - ] - }), - terser({ - format: { - comments: false - } - }) - ] - } -]; - -const componentBuilds = compatPkg.components - // The "app" component is treated differently because it doesn't depend on itself. - .filter(component => component !== 'app') - .map(component => { - const pkg = require(`${__dirname}/${component}/package.json`); - return [ - { - input: `${__dirname}/${component}/index.ts`, - output: [ - { - file: resolve(__dirname, component, pkg.main), - format: 'cjs', - sourcemap: true - }, - { - file: resolve(__dirname, component, pkg.module), - format: 'es', - sourcemap: true - } - ], - plugins: [...plugins, typescriptPlugin], - external - }, - { - input: `${__dirname}/${component}/index.ts`, - output: createUmdOutputConfig(`firebase-${component}-compat.js`), - plugins: [ - ...plugins, - typescriptPluginUMD, - alias({ - entries: [ - { - find: `@firebase/${component}`, - replacement: `@firebase/${component}-exp` - }, - { - find: '@firebase/installations', - replacement: '@firebase/installations-exp' - }, - { - // hack to locate firestore-compat - find: '@firebase/firestore-compat', - replacement: 'firestore-compat' - }, - { - // hack to locate storage-compat - find: '@firebase/storage-compat', - replacement: 'storage-compat' - }, - { - // hack to locate database-compat - find: '@firebase/database-compat', - replacement: 'database-compat' - } - ] - }), - terser({ - format: { - comments: false - } - }) - ], - external: ['@firebase/app-compat', '@firebase/app'] - } - ]; - }) - .reduce((a, b) => a.concat(b), []); - -const aliasForCompleteCDNBuild = compatPkg.components - .filter(component => { - return ( - component !== 'firestore' && - component !== 'storage' && - component !== 'database' - ); - }) - .map(component => ({ - find: `@firebase/${component}`, - replacement: `@firebase/${component}-exp` - })) - .concat([ - { - find: '@firebase/installations', - replacement: '@firebase/installations-exp' - }, - { - // hack to locate firestore-compat - find: '@firebase/firestore-compat', - replacement: 'firestore-compat' - }, - { - // hack to locate storage-compat - find: '@firebase/storage-compat', - replacement: 'storage-compat' - }, - { - // hack to locate database-compat - find: '@firebase/database-compat', - replacement: 'database-compat' - } - ]); - -/** - * Complete Package Builds - */ -const completeBuilds = [ - /** - * App Browser Builds - */ - { - input: `${__dirname}/index.ts`, - output: [ - { - file: resolve(__dirname, compatPkg.module), - format: 'es', - sourcemap: true - } - ], - plugins: [...plugins, typescriptPluginUMD], - external - }, - { - input: `${__dirname}/index.cdn.ts`, - output: { - file: 'firebase-compat.js', - format: 'umd', - // disable sourcemap, otherwise build will fail with Error: Multiple conflicting contents for sourcemap source - // TODO: I think it's related to the alias() we are using, so let's try to reenable it for GA. - sourcemap: false, - name: GLOBAL_NAME - }, - plugins: [ - ...plugins, - typescriptPluginUMD, - terser(), - alias({ - entries: aliasForCompleteCDNBuild - }) - ] - }, - /** - * App Node.js Builds - */ - { - input: `${__dirname}/index.node.ts`, - output: { - file: resolve(__dirname, compatPkg.main), - format: 'cjs', - sourcemap: true - }, - plugins: [...plugins, typescriptPluginUMD], - external - }, - /** - * App React Native Builds - */ - { - input: `${__dirname}/index.rn.ts`, - output: { - file: resolve(__dirname, compatPkg['react-native']), - format: 'cjs', - sourcemap: true - }, - plugins: [...plugins, typescriptPluginUMD], - external - }, - /** - * Performance script Build - */ - { - input: `${__dirname}/index.perf.ts`, - output: { - file: 'firebase-performance-standalone-compat.js', - format: 'umd', - sourcemap: true, - name: GLOBAL_NAME - }, - plugins: [ - sourcemaps(), - resolveModule({ - mainFields: ['lite-esm5', 'esm5', 'module'] - }), - typescriptPluginUMD, - json(), - commonjs(), - terser({ - format: { - comments: false - } - }), - alias({ - entries: [ - { - find: '@firebase/app', - replacement: '@firebase/app-exp' - }, - { - find: `@firebase/performance`, - replacement: `@firebase/performance-exp` - }, - { - find: '@firebase/installations', - replacement: '@firebase/installations-exp' - } - ] - }) - ] - }, - /** - * Performance script Build in ES2017 - */ - { - input: `${__dirname}/index.perf.ts`, - output: { - file: 'firebase-performance-standalone-compat.es2017.js', - format: 'umd', - sourcemap: true, - name: GLOBAL_NAME - }, - plugins: [ - sourcemaps(), - resolveModule({ - mainFields: ['lite', 'module', 'main'] - }), - rollupTypescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017', - declaration: false - } - } - }), - json({ - preferConst: true - }), - commonjs(), - terser({ - format: { - comments: false - } - }), - alias({ - entries: [ - { - find: '@firebase/app', - replacement: '@firebase/app-exp' - }, - { - find: `@firebase/performance`, - replacement: `@firebase/performance-exp` - }, - { - find: '@firebase/installations', - replacement: '@firebase/installations-exp' - } - ] - }) - ] - } -]; - -export default [...appBuilds, ...componentBuilds, ...completeBuilds]; diff --git a/packages-exp/firebase-exp/compat/storage/package.json b/packages-exp/firebase-exp/compat/storage/package.json deleted file mode 100644 index a286a2f82cd..00000000000 --- a/packages-exp/firebase-exp/compat/storage/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/compat/storage", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/compat/storage/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/database/package.json b/packages-exp/firebase-exp/database/package.json deleted file mode 100644 index 535c4f4e261..00000000000 --- a/packages-exp/firebase-exp/database/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/database", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/database/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/firestore/lite/package.json b/packages-exp/firebase-exp/firestore/lite/package.json deleted file mode 100644 index d0b413bbf8b..00000000000 --- a/packages-exp/firebase-exp/firestore/lite/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/firestore/lite", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/firestore/lite/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/firestore/package.json b/packages-exp/firebase-exp/firestore/package.json deleted file mode 100644 index 25cc8b85aa6..00000000000 --- a/packages-exp/firebase-exp/firestore/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/firestore", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/firestore/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/functions/index.ts b/packages-exp/firebase-exp/functions/index.ts deleted file mode 100644 index 7b0fa9efc45..00000000000 --- a/packages-exp/firebase-exp/functions/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from '@firebase/functions-exp'; diff --git a/packages-exp/firebase-exp/functions/package.json b/packages-exp/firebase-exp/functions/package.json deleted file mode 100644 index aa6f2eea1fe..00000000000 --- a/packages-exp/firebase-exp/functions/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/functions", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/functions/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/messaging/index.ts b/packages-exp/firebase-exp/messaging/index.ts deleted file mode 100644 index 4a8d59d79ac..00000000000 --- a/packages-exp/firebase-exp/messaging/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from '@firebase/messaging-exp'; diff --git a/packages-exp/firebase-exp/messaging/package.json b/packages-exp/firebase-exp/messaging/package.json deleted file mode 100644 index 6579c826379..00000000000 --- a/packages-exp/firebase-exp/messaging/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/messaging", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/messaging/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/messaging/sw/index.ts b/packages-exp/firebase-exp/messaging/sw/index.ts deleted file mode 100644 index bac2a36a95e..00000000000 --- a/packages-exp/firebase-exp/messaging/sw/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { - onBackgroundMessage, - getMessaging, - isSupported -} from '@firebase/messaging-exp/sw'; diff --git a/packages-exp/firebase-exp/messaging/sw/package.json b/packages-exp/firebase-exp/messaging/sw/package.json deleted file mode 100644 index f92db94c9f8..00000000000 --- a/packages-exp/firebase-exp/messaging/sw/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/messaging/sw", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/messaging/sw/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/package.json b/packages-exp/firebase-exp/package.json deleted file mode 100644 index 663d635f3f1..00000000000 --- a/packages-exp/firebase-exp/package.json +++ /dev/null @@ -1,277 +0,0 @@ -{ - "name": "firebase-exp", - "version": "9.0.0-beta.8", - "private": true, - "description": "Firebase JavaScript library for web and Node.js", - "author": "Firebase (https://firebase.google.com/)", - "license": "Apache-2.0", - "homepage": "https://firebase.google.com/", - "keywords": [ - "authentication", - "database", - "Firebase", - "firebase", - "realtime", - "storage", - "performance", - "remote-config" - ], - "files": [ - "**/dist/", - "**/package.json", - "/firebase*.js", - "/firebase*.map", - "compat/index.d.ts" - ], - "exports": { - "./analytics": { - "node": { - "require": "./analytics/dist/index.cjs", - "import": "./analytics/dist/index.esm.js" - }, - "default": "./analytics/dist/index.esm.js" - }, - "./app": { - "node": { - "require": "./app/dist/index.cjs", - "import": "./app/dist/index.esm.js" - }, - "default": "./app/dist/index.esm.js" - }, - "./app-check": { - "node": { - "require": "./app-check/dist/index.cjs", - "import": "./app-check/dist/index.esm.js" - }, - "default": "./app-check/dist/index.esm.js" - }, - "./auth": { - "node": { - "require": "./auth/dist/index.cjs", - "import": "./auth/dist/index.esm.js" - }, - "default": "./auth/dist/index.esm.js" - }, - "./auth/cordova": { - "node": { - "require": "./auth/cordova/dist/index.cjs", - "import": "./auth/cordova/dist/index.esm.js" - }, - "default": "./auth/cordova/dist/index.esm.js" - }, - "./auth/react-native": { - "node": { - "require": "./auth/react-native/dist/index.cjs", - "import": "./auth/react-native/dist/index.esm.js" - }, - "default": "./auth/react-native/dist/index.esm.js" - }, - "./database": { - "node": { - "require": "./database/dist/index.cjs", - "import": "./database/dist/index.esm.js" - }, - "default": "./database/dist/index.esm.js" - }, - "./firestore": { - "node": { - "require": "./firestore/dist/index.cjs", - "import": "./firestore/dist/index.esm.js" - }, - "default": "./firestore/dist/index.esm.js" - }, - "./firestore/lite": { - "node": { - "require": "./firestore/lite/dist/index.cjs", - "import": "./firestore/lite/dist/index.esm.js" - }, - "default": "./firestore/lite/dist/index.esm.js" - }, - "./functions": { - "node": { - "require": "./functions/dist/index.cjs", - "import": "./functions/dist/index.esm.js" - }, - "default": "./functions/dist/index.esm.js" - }, - "./messaging": { - "node": { - "require": "./messaging/dist/index.cjs", - "import": "./messaging/dist/index.esm.js" - }, - "default": "./messaging/dist/index.esm.js" - }, - "./messaging/sw": { - "node": { - "require": "./messaging/sw/dist/index.cjs", - "import": "./messaging/sw/dist/index.esm.js" - }, - "default": "./messaging/sw/dist/index.esm.js" - }, - "./performance": { - "node": { - "require": "./performance/dist/index.cjs", - "import": "./performance/dist/index.esm.js" - }, - "default": "./performance/dist/index.esm.js" - }, - "./remote-config": { - "node": { - "require": "./remote-config/dist/index.cjs", - "import": "./remote-config/dist/index.esm.js" - }, - "default": "./remote-config/dist/index.esm.js" - }, - "./storage": { - "node": { - "require": "./storage/dist/index.cjs", - "import": "./storage/dist/index.esm.js" - }, - "default": "./storage/dist/index.esm.js" - }, - "./compat/analytics": { - "node": { - "require": "./compat/analytics/dist/index.cjs", - "import": "./compat/analytics/dist/index.esm.js" - }, - "default": "./compat/analytics/dist/index.esm.js" - }, - "./compat/app": { - "node": { - "require": "./compat/app/dist/index.cjs", - "import": "./compat/app/dist/index.esm.js" - }, - "default": "./compat/app/dist/index.esm.js" - }, - "./compat/app-check": { - "node": { - "require": "./compat/app-check/dist/index.cjs", - "import": "./compat/app-check/dist/index.esm.js" - }, - "default": "./compat/app-check/dist/index.esm.js" - }, - "./compat/auth": { - "node": { - "require": "./compat/auth/dist/index.cjs", - "import": "./compat/auth/dist/index.esm.js" - }, - "default": "./compat/auth/dist/index.esm.js" - }, - "./compat/database": { - "node": { - "require": "./compat/database/dist/index.cjs", - "import": "./compat/database/dist/index.esm.js" - }, - "default": "./compat/database/dist/index.esm.js" - }, - "./compat/firestore": { - "node": { - "require": "./compat/firestore/dist/index.cjs", - "import": "./compat/firestore/dist/index.esm.js" - }, - "default": "./compat/firestore/dist/index.esm.js" - }, - "./compat/functions": { - "node": { - "require": "./compat/functions/dist/index.cjs", - "import": "./compat/functions/dist/index.esm.js" - }, - "default": "./compat/functions/dist/index.esm.js" - }, - "./compat/messaging": { - "node": { - "require": "./compat/messaging/dist/index.cjs", - "import": "./compat/messaging/dist/index.esm.js" - }, - "default": "./compat/messaging/dist/index.esm.js" - }, - "./compat/performance": { - "node": { - "require": "./compat/performance/dist/index.cjs", - "import": "./compat/performance/dist/index.esm.js" - }, - "default": "./compat/performance/dist/index.esm.js" - }, - "./compat/remote-config": { - "node": { - "require": "./compat/remote-config/dist/index.cjs", - "import": "./compat/remote-config/dist/index.esm.js" - }, - "default": "./compat/remote-config/dist/index.esm.js" - }, - "./compat/storage": { - "node": { - "require": "./compat/storage/dist/index.cjs", - "import": "./compat/storage/dist/index.esm.js" - }, - "default": "./compat/storage/dist/index.esm.js" - } - }, - "repository": { - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "scripts": { - "build": "rollup -c && yarn build:compat", - "build:release": "rollup -c rollup.config.release.js && yarn build:compat:release", - "build:compat": "rollup -c compat/rollup.config.js", - "build:compat:release": "rollup -c compat/rollup.config.release.js", - "dev": "rollup -c -w", - "test": "echo 'No test suite for firebase wrapper'", - "test:ci": "echo 'No test suite for firebase wrapper'" - }, - "dependencies": { - "@firebase/analytics-exp": "0.0.900", - "@firebase/analytics-compat": "0.0.900", - "@firebase/app-exp": "0.0.900", - "@firebase/app-compat": "0.0.900", - "@firebase/app-check-exp": "0.0.900", - "@firebase/app-check-compat": "0.0.900", - "@firebase/auth-exp": "0.0.900", - "@firebase/auth-compat": "0.0.900", - "@firebase/database": "0.11.0", - "@firebase/functions-exp": "0.0.900", - "@firebase/functions-compat": "0.0.900", - "@firebase/firestore": "2.4.0", - "@firebase/storage": "0.7.0", - "@firebase/performance-exp": "0.0.900", - "@firebase/performance-compat": "0.0.900", - "@firebase/remote-config-exp": "0.0.900", - "@firebase/remote-config-compat": "0.0.900", - "@firebase/messaging-exp": "0.0.900", - "@firebase/messaging-compat": "0.0.900" - }, - "devDependencies": { - "rollup": "2.52.2", - "@rollup/plugin-commonjs": "17.1.0", - "rollup-plugin-license": "2.5.0", - "@rollup/plugin-node-resolve": "11.2.0", - "rollup-plugin-sourcemaps": "0.6.3", - "rollup-plugin-terser": "7.0.2", - "rollup-plugin-typescript2": "0.30.0", - "rollup-plugin-uglify": "6.0.4", - "@rollup/plugin-alias": "3.1.2", - "gulp": "4.0.2", - "gulp-sourcemaps": "3.0.0", - "gulp-concat": "2.6.1", - "typescript": "4.2.2" - }, - "components": [ - "analytics", - "app", - "app-check", - "auth", - "auth/cordova", - "auth/react-native", - "functions", - "firestore", - "firestore/lite", - "storage", - "performance", - "remote-config", - "messaging", - "messaging/sw", - "database" - ], - "type": "module" -} diff --git a/packages-exp/firebase-exp/performance/index.ts b/packages-exp/firebase-exp/performance/index.ts deleted file mode 100644 index e1ea2b74db5..00000000000 --- a/packages-exp/firebase-exp/performance/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from '@firebase/performance-exp'; diff --git a/packages-exp/firebase-exp/performance/package.json b/packages-exp/firebase-exp/performance/package.json deleted file mode 100644 index d5fef2a2102..00000000000 --- a/packages-exp/firebase-exp/performance/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/performance", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/performance/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/remote-config/index.ts b/packages-exp/firebase-exp/remote-config/index.ts deleted file mode 100644 index 73b26c811e9..00000000000 --- a/packages-exp/firebase-exp/remote-config/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from '@firebase/remote-config-exp'; diff --git a/packages-exp/firebase-exp/remote-config/package.json b/packages-exp/firebase-exp/remote-config/package.json deleted file mode 100644 index 59559ee4973..00000000000 --- a/packages-exp/firebase-exp/remote-config/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/remote-config", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/remote-config/index.d.ts", - "type": "module" -} diff --git a/packages-exp/firebase-exp/rollup.config.js b/packages-exp/firebase-exp/rollup.config.js deleted file mode 100644 index b742052c059..00000000000 --- a/packages-exp/firebase-exp/rollup.config.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import appPkg from './app/package.json'; -import commonjs from '@rollup/plugin-commonjs'; -import json from '@rollup/plugin-json'; -import pkg from './package.json'; -import { resolve } from 'path'; -import resolveModule from '@rollup/plugin-node-resolve'; -import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; -import sourcemaps from 'rollup-plugin-sourcemaps'; -import typescript from 'typescript'; -import alias from '@rollup/plugin-alias'; - -const external = Object.keys(pkg.dependencies || {}); -const plugins = [sourcemaps(), resolveModule(), json(), commonjs()]; - -const typescriptPlugin = rollupTypescriptPlugin({ - typescript -}); - -const typescriptPluginCDN = rollupTypescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - declaration: false - } - } -}); - -/** - * Individual Component Builds - */ -const appBuilds = [ - /** - * App Browser Builds - */ - { - input: 'app/index.ts', - output: [ - { file: resolve('app', appPkg.main), format: 'cjs', sourcemap: true }, - { file: resolve('app', appPkg.module), format: 'es', sourcemap: true } - ], - plugins: [...plugins, typescriptPlugin], - external - } -]; - -const componentBuilds = pkg.components - // The "app" component is treated differently because it doesn't depend on itself. - .filter(component => component !== 'app') - .map(component => { - const pkg = require(`./${component}/package.json`); - return [ - { - input: `${component}/index.ts`, - output: [ - { - file: resolve(component, pkg.main), - format: 'cjs', - sourcemap: true - }, - { - file: resolve(component, pkg.module), - format: 'es', - sourcemap: true - } - ], - plugins: [...plugins, typescriptPlugin], - external - } - ]; - }) - .reduce((a, b) => a.concat(b), []); - -/** - * CDN script builds - */ -const FIREBASE_APP_URL = `https://www.gstatic.com/firebasejs/${pkg.version}/firebase-app.js`; -const cdnBuilds = [ - { - input: 'app/index.cdn.ts', - output: { - file: 'firebase-app.js', - sourcemap: true, - format: 'es' - }, - plugins: [...plugins, typescriptPluginCDN] - }, - ...pkg.components - .filter(component => component !== 'app') - .map(component => { - // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js - // Otherwise, we will create a directory with '/' in the name. - const componentName = component.replace('/', '-'); - - return { - input: `${component}/index.ts`, - output: { - file: `firebase-${componentName}.js`, - sourcemap: true, - format: 'es' - }, - plugins: [ - ...plugins, - typescriptPluginCDN, - alias({ - entries: { - '@firebase/app': FIREBASE_APP_URL - } - }) - ], - external: [FIREBASE_APP_URL] - }; - }) -]; - -export default [...appBuilds, ...componentBuilds, ...cdnBuilds]; diff --git a/packages-exp/firebase-exp/rollup.config.release.js b/packages-exp/firebase-exp/rollup.config.release.js deleted file mode 100644 index 89b4be10c6d..00000000000 --- a/packages-exp/firebase-exp/rollup.config.release.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import appPkg from './app/package.json'; -import commonjs from '@rollup/plugin-commonjs'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import json from '@rollup/plugin-json'; -import pkg from './package.json'; -import { resolve } from 'path'; -import resolveModule from '@rollup/plugin-node-resolve'; -import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; -import sourcemaps from 'rollup-plugin-sourcemaps'; -import typescript from 'typescript'; -import alias from '@rollup/plugin-alias'; -import { terser } from 'rollup-plugin-terser'; - -// remove -exp from dependencies name -const deps = Object.keys(pkg.dependencies || {}).map(name => - name.replace('-exp', '') -); - -const plugins = [sourcemaps(), resolveModule(), json(), commonjs()]; - -const typescriptPlugin = rollupTypescriptPlugin({ - typescript, - transformers: [importPathTransformer] -}); - -const typescriptPluginCDN = rollupTypescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - declaration: false - } - } -}); - -/** - * Individual Component Builds - */ -const appBuilds = [ - /** - * App Browser Builds - */ - { - input: 'app/index.ts', - output: [ - { file: resolve('app', appPkg.main), format: 'cjs', sourcemap: true }, - { file: resolve('app', appPkg.module), format: 'es', sourcemap: true } - ], - plugins: [...plugins, typescriptPlugin], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -const componentBuilds = pkg.components - // The "app" component is treated differently because it doesn't depend on itself. - .filter(component => component !== 'app') - .map(component => { - const pkg = require(`./${component}/package.json`); - - return [ - { - input: `${component}/index.ts`, - output: [ - { - file: resolve(component, pkg.main), - format: 'cjs', - sourcemap: true - }, - { - file: resolve(component, pkg.module), - format: 'es', - sourcemap: true - } - ], - plugins: [...plugins, typescriptPlugin], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } - ]; - }) - .reduce((a, b) => a.concat(b), []); - -/** - * CDN script builds - */ -const FIREBASE_APP_URL = `https://www.gstatic.com/firebasejs/${pkg.version}/firebase-app.js`; -const cdnBuilds = [ - { - input: 'app/index.cdn.ts', - output: { - file: 'firebase-app.js', - sourcemap: true, - format: 'es' - }, - plugins: [ - ...plugins, - typescriptPluginCDN, - terser({ - format: { - comments: false - } - }) - ] - }, - ...pkg.components - .filter(component => component !== 'app') - .map(component => { - const pkg = require(`./${component}/package.json`); - // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js - // Otherwise, we will create a directory with '/' in the name. - const componentName = component.replace('/', '-'); - - return { - input: `${component}/index.ts`, - output: { - file: `firebase-${componentName}.js`, - sourcemap: true, - format: 'es' - }, - plugins: [ - ...plugins, - typescriptPluginCDN, - alias({ - entries: { - '@firebase/app': FIREBASE_APP_URL, - '@firebase/installations': '@firebase/installations-exp' - } - }), - terser({ - format: { - comments: false - } - }) - ], - external: [FIREBASE_APP_URL] - }; - }) -]; -export default [...appBuilds, ...componentBuilds, ...cdnBuilds]; diff --git a/packages-exp/firebase-exp/storage/index.ts b/packages-exp/firebase-exp/storage/index.ts deleted file mode 100644 index 196d6e4ecd7..00000000000 --- a/packages-exp/firebase-exp/storage/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from '@firebase/storage'; diff --git a/packages-exp/firebase-exp/storage/package.json b/packages-exp/firebase-exp/storage/package.json deleted file mode 100644 index 78c66914d69..00000000000 --- a/packages-exp/firebase-exp/storage/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "firebase-exp/storage", - "main": "dist/index.cjs", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "dist/storage/index.d.ts", - "type": "module" -} diff --git a/packages-exp/functions-compat/rollup.config.base.js b/packages-exp/functions-compat/rollup.config.base.js deleted file mode 100644 index a8ac2951f69..00000000000 --- a/packages-exp/functions-compat/rollup.config.base.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/functions' -]; - -/** - * ES5 Builds - */ -export function getEs5Builds(additionalTypescriptPlugins = {}) { - const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - abortOnError: false, - ...additionalTypescriptPlugins - }), - json() - ]; - return [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - /** - * Node.js Build - */ - { - input: 'src/index.node.ts', - output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } - ]; -} - -/** - * ES2017 Builds - */ -export function getEs2017Builds(additionalTypescriptPlugins = {}) { - const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - abortOnError: false, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - ...additionalTypescriptPlugins - }), - json({ preferConst: true }) - ]; - return [ - { - /** - * Browser Build - */ - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } - ]; -} - -export function getAllBuilds(additionalTypescriptPlugins = {}) { - return [ - ...getEs5Builds(additionalTypescriptPlugins), - ...getEs2017Builds(additionalTypescriptPlugins) - ]; -} diff --git a/packages-exp/functions-compat/rollup.config.js b/packages-exp/functions-compat/rollup.config.js deleted file mode 100644 index 7746175a9a6..00000000000 --- a/packages-exp/functions-compat/rollup.config.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getAllBuilds } from './rollup.config.base'; - -// eslint-disable-next-line import/no-default-export -export default getAllBuilds({}); diff --git a/packages-exp/functions-compat/rollup.config.release.js b/packages-exp/functions-compat/rollup.config.release.js deleted file mode 100644 index d364683678a..00000000000 --- a/packages-exp/functions-compat/rollup.config.release.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { getAllBuilds } from './rollup.config.base'; - -// eslint-disable-next-line import/no-default-export -export default getAllBuilds({ - clean: true, - transformers: [importPathTransformer] -}); diff --git a/packages-exp/functions-exp/package.json b/packages-exp/functions-exp/package.json deleted file mode 100644 index aa5f1e6954e..00000000000 --- a/packages-exp/functions-exp/package.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "@firebase/functions-exp", - "version": "0.0.900", - "description": "", - "private": true, - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.node.cjs.js", - "browser": "dist/index.esm2017.js", - "module": "dist/index.esm2017.js", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:deps": "lerna run --scope @firebase/functions-exp --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js && yarn api-report", - "dev": "rollup -c -w", - "test": "run-p lint test:all", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", - "test:all": "run-p test:browser test:node", - "test:browser": "karma start --single-run", - "test:browser:debug": "karma start --browsers=Chrome --auto-watch", - "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", - "test:emulator": "env FIREBASE_FUNCTIONS_EMULATOR_ORIGIN=http://localhost:5005 run-p test:node", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc" - }, - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-exp": "0.x" - }, - "devDependencies": { - "@firebase/app-exp": "0.0.900", - "rollup": "2.52.2", - "@rollup/plugin-json": "4.1.0", - "rollup-plugin-typescript2": "0.30.0", - "typescript": "4.2.2" - }, - "repository": { - "directory": "packages/functions", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "typings": "dist/functions-exp-public.d.ts", - "dependencies": { - "@firebase/component": "0.5.6", - "@firebase/messaging-interop-types": "0.0.1", - "@firebase/auth-interop-types": "0.1.6", - "@firebase/app-check-interop-types": "0.1.0", - "@firebase/util": "1.3.0", - "node-fetch": "2.6.1", - "tslib": "^2.1.0" - }, - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - }, - "esm5": "dist/index.esm.js" -} \ No newline at end of file diff --git a/packages-exp/functions-exp/rollup.config.js b/packages-exp/functions-exp/rollup.config.js deleted file mode 100644 index 63db899d25f..00000000000 --- a/packages-exp/functions-exp/rollup.config.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/functions-exp/rollup.config.release.js b/packages-exp/functions-exp/rollup.config.release.js deleted file mode 100644 index 1e3b338e4b5..00000000000 --- a/packages-exp/functions-exp/rollup.config.release.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: false - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: false - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/functions-exp/rollup.shared.js b/packages-exp/functions-exp/rollup.shared.js deleted file mode 100644 index 15d16f45300..00000000000 --- a/packages-exp/functions-exp/rollup.shared.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -export const es5BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - /** - * Node.js Build - */ - { - input: 'src/index.node.ts', - output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -export const es2017BuildsNoPlugin = [ - { - /** - * Browser Build - */ - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/functions-exp/src/config.ts b/packages-exp/functions-exp/src/config.ts deleted file mode 100644 index 86f02b598b5..00000000000 --- a/packages-exp/functions-exp/src/config.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { _registerComponent } from '@firebase/app-exp'; -import { FunctionsService } from './service'; -import { - Component, - ComponentType, - ComponentContainer, - InstanceFactory -} from '@firebase/component'; -import { FUNCTIONS_TYPE } from './constants'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -import { MessagingInternalComponentName } from '@firebase/messaging-interop-types'; - -const AUTH_INTERNAL_NAME: FirebaseAuthInternalName = 'auth-internal'; -const APP_CHECK_INTERNAL_NAME: AppCheckInternalComponentName = - 'app-check-internal'; -const MESSAGING_INTERNAL_NAME: MessagingInternalComponentName = - 'messaging-internal'; - -export function registerFunctions(fetchImpl: typeof fetch): void { - const factory: InstanceFactory<'functions-exp'> = ( - container: ComponentContainer, - { instanceIdentifier: regionOrCustomDomain } - ) => { - // Dependencies - const app = container.getProvider('app-exp').getImmediate(); - const authProvider = container.getProvider(AUTH_INTERNAL_NAME); - const messagingProvider = container.getProvider(MESSAGING_INTERNAL_NAME); - const appCheckProvider = container.getProvider(APP_CHECK_INTERNAL_NAME); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return new FunctionsService( - app, - authProvider, - messagingProvider, - appCheckProvider, - regionOrCustomDomain, - fetchImpl - ); - }; - - _registerComponent( - new Component( - FUNCTIONS_TYPE, - factory, - ComponentType.PUBLIC - ).setMultipleInstances(true) - ); -} diff --git a/packages-exp/functions-exp/src/context.ts b/packages-exp/functions-exp/src/context.ts deleted file mode 100644 index b40d093be3c..00000000000 --- a/packages-exp/functions-exp/src/context.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { - FirebaseAuthInternal, - FirebaseAuthInternalName -} from '@firebase/auth-interop-types'; -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Provider } from '@firebase/component'; -import { - AppCheckInternalComponentName, - FirebaseAppCheckInternal -} from '@firebase/app-check-interop-types'; -import { - MessagingInternal, - MessagingInternalComponentName -} from '@firebase/messaging-interop-types'; - -/** - * The metadata that should be supplied with function calls. - * @internal - */ -export interface Context { - authToken?: string; - messagingToken?: string; - appCheckToken: string | null; -} - -/** - * Helper class to get metadata that should be included with a function call. - * @internal - */ -export class ContextProvider { - private auth: FirebaseAuthInternal | null = null; - private messaging: MessagingInternal | null = null; - private appCheck: FirebaseAppCheckInternal | null = null; - constructor( - authProvider: Provider, - messagingProvider: Provider, - appCheckProvider: Provider - ) { - this.auth = authProvider.getImmediate({ optional: true }); - this.messaging = messagingProvider.getImmediate({ - optional: true - }); - - if (!this.auth) { - authProvider.get().then( - auth => (this.auth = auth), - () => { - /* get() never rejects */ - } - ); - } - - if (!this.messaging) { - messagingProvider.get().then( - messaging => (this.messaging = messaging), - () => { - /* get() never rejects */ - } - ); - } - - if (!this.appCheck) { - appCheckProvider.get().then( - appCheck => (this.appCheck = appCheck), - () => { - /* get() never rejects */ - } - ); - } - } - - async getAuthToken(): Promise { - if (!this.auth) { - return undefined; - } - - try { - const token = await this.auth.getToken(); - return token?.accessToken; - } catch (e) { - // If there's any error when trying to get the auth token, leave it off. - return undefined; - } - } - - async getMessagingToken(): Promise { - if ( - !this.messaging || - !('Notification' in self) || - Notification.permission !== 'granted' - ) { - return undefined; - } - - try { - return await this.messaging.getToken(); - } catch (e) { - // We don't warn on this, because it usually means messaging isn't set up. - // console.warn('Failed to retrieve instance id token.', e); - - // If there's any error when trying to get the token, leave it off. - return undefined; - } - } - - async getAppCheckToken(): Promise { - if (this.appCheck) { - const result = await this.appCheck.getToken(); - // If getToken() fails, it will still return a dummy token that also has - // an error field containing the error message. We will send any token - // provided here and show an error if/when it is rejected by the functions - // endpoint. - return result.token; - } - return null; - } - - async getContext(): Promise { - const authToken = await this.getAuthToken(); - const messagingToken = await this.getMessagingToken(); - const appCheckToken = await this.getAppCheckToken(); - return { authToken, messagingToken, appCheckToken }; - } -} diff --git a/packages-exp/functions-exp/src/serializer.ts b/packages-exp/functions-exp/src/serializer.ts deleted file mode 100644 index e6247ec34d1..00000000000 --- a/packages-exp/functions-exp/src/serializer.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -const LONG_TYPE = 'type.googleapis.com/google.protobuf.Int64Value'; -const UNSIGNED_LONG_TYPE = 'type.googleapis.com/google.protobuf.UInt64Value'; - -function mapValues( - // { [k: string]: unknown } is no longer a wildcard assignment target after typescript 3.5 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - o: { [key: string]: any }, - f: (arg0: unknown) => unknown -): object { - const result: { [key: string]: unknown } = {}; - for (const key in o) { - if (o.hasOwnProperty(key)) { - result[key] = f(o[key]); - } - } - return result; -} - -/** - * Takes data and encodes it in a JSON-friendly way, such that types such as - * Date are preserved. - * @internal - * @param data - Data to encode. - */ -export function encode(data: unknown): unknown { - if (data == null) { - return null; - } - if (data instanceof Number) { - data = data.valueOf(); - } - if (typeof data === 'number' && isFinite(data)) { - // Any number in JS is safe to put directly in JSON and parse as a double - // without any loss of precision. - return data; - } - if (data === true || data === false) { - return data; - } - if (Object.prototype.toString.call(data) === '[object String]') { - return data; - } - if (data instanceof Date) { - return data.toISOString(); - } - if (Array.isArray(data)) { - return data.map(x => encode(x)); - } - if (typeof data === 'function' || typeof data === 'object') { - return mapValues(data!, x => encode(x)); - } - // If we got this far, the data is not encodable. - throw new Error('Data cannot be encoded in JSON: ' + data); -} - -/** - * Takes data that's been encoded in a JSON-friendly form and returns a form - * with richer datatypes, such as Dates, etc. - * @internal - * @param json - JSON to convert. - */ -export function decode(json: unknown): unknown { - if (json == null) { - return json; - } - if ((json as { [key: string]: unknown })['@type']) { - switch ((json as { [key: string]: unknown })['@type']) { - case LONG_TYPE: - // Fall through and handle this the same as unsigned. - case UNSIGNED_LONG_TYPE: { - // Technically, this could work return a valid number for malformed - // data if there was a number followed by garbage. But it's just not - // worth all the extra code to detect that case. - const value = Number((json as { [key: string]: unknown })['value']); - if (isNaN(value)) { - throw new Error('Data cannot be decoded from JSON: ' + json); - } - return value; - } - default: { - throw new Error('Data cannot be decoded from JSON: ' + json); - } - } - } - if (Array.isArray(json)) { - return json.map(x => decode(x)); - } - if (typeof json === 'function' || typeof json === 'object') { - return mapValues(json!, x => decode(x)); - } - // Anything else is safe to return. - return json; -} diff --git a/packages-exp/functions-exp/test/utils.ts b/packages-exp/functions-exp/test/utils.ts deleted file mode 100644 index 23829506368..00000000000 --- a/packages-exp/functions-exp/test/utils.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseOptions, FirebaseApp } from '@firebase/app-exp'; -import { Provider, ComponentContainer } from '@firebase/component'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -import { FunctionsService } from '../src/service'; -import { connectFunctionsEmulator } from '../src/api'; -import nodeFetch from 'node-fetch'; -import { MessagingInternalComponentName } from '../../../packages/messaging-interop-types'; - -export function makeFakeApp(options: FirebaseOptions = {}): FirebaseApp { - options = { - apiKey: 'apiKey', - projectId: 'projectId', - authDomain: 'authDomain', - messagingSenderId: '1234567890', - databaseURL: 'databaseUrl', - storageBucket: 'storageBucket', - appId: '1:777777777777:web:d93b5ca1475efe57', - ...options - }; - return { - name: 'appName', - options, - automaticDataCollectionEnabled: true - }; -} - -export function createTestService( - app: FirebaseApp, - region?: string, - authProvider = new Provider( - 'auth-internal', - new ComponentContainer('test') - ), - messagingProvider = new Provider( - 'messaging-internal', - new ComponentContainer('test') - ), - appCheckProvider = new Provider( - 'app-check-internal', - new ComponentContainer('test') - ) -): FunctionsService { - const fetchImpl: typeof fetch = - typeof window !== 'undefined' ? fetch.bind(window) : (nodeFetch as any); - const functions = new FunctionsService( - app, - authProvider, - messagingProvider, - appCheckProvider, - region, - fetchImpl - ); - const useEmulator = !!process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN; - if (useEmulator) { - const url = new URL(process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN!); - connectFunctionsEmulator( - functions, - url.hostname, - Number.parseInt(url.port, 10) - ); - } - return functions; -} diff --git a/packages-exp/functions-exp/tsconfig.json b/packages-exp/functions-exp/tsconfig.json deleted file mode 100644 index a06ed9a374c..00000000000 --- a/packages-exp/functions-exp/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "outDir": "dist" - }, - "exclude": [ - "dist/**/*" - ] -} \ No newline at end of file diff --git a/packages-exp/installations-compat/rollup.config.js b/packages-exp/installations-compat/rollup.config.js deleted file mode 100644 index d66e464f546..00000000000 --- a/packages-exp/installations-compat/rollup.config.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [typescriptPlugin({ typescript }), json()]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/installations-compat/rollup.config.release.js b/packages-exp/installations-compat/rollup.config.release.js deleted file mode 100644 index 5050932f4a4..00000000000 --- a/packages-exp/installations-compat/rollup.config.release.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: false - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: false - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/installations-exp/karma.conf.js b/packages-exp/installations-exp/karma.conf.js deleted file mode 100644 index 1699a0681ec..00000000000 --- a/packages-exp/installations-exp/karma.conf.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const karmaBase = require('../../config/karma.base'); - -const files = [`src/**/*.test.ts`]; - -module.exports = function (config) { - const karmaConfig = { - ...karmaBase, - // files to load into karma - files, - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha'] - }; - - config.set(karmaConfig); -}; - -module.exports.files = files; diff --git a/packages-exp/installations-exp/package.json b/packages-exp/installations-exp/package.json deleted file mode 100644 index 94e29501b6d..00000000000 --- a/packages-exp/installations-exp/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "@firebase/installations-exp", - "version": "0.0.900", - "private": true, - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.cjs.js", - "module": "dist/index.esm2017.js", - "browser": "dist/index.esm2017.js", - "typings": "dist/src/index.d.ts", - "license": "Apache-2.0", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:deps": "lerna run --scope @firebase/installations-exp --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", - "dev": "rollup -c -w", - "test": "yarn type-check && yarn test:karma && yarn lint", - "test:ci": "node ../../scripts/run_tests_in_ci.js", - "test:karma": "karma start --single-run", - "test:debug": "karma start --browsers=Chrome --auto-watch", - "type-check": "tsc -p . --noEmit", - "serve": "yarn serve:build && yarn serve:host", - "serve:build": "rollup -c test-app/rollup.config.js", - "serve:host": "http-server -c-1 test-app", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc", - "typings:public": "node ../../scripts/exp/use_typings.js ./dist/installations-exp-public.d.ts", - "typings:internal": "node ../../scripts/exp/use_typings.js ./dist/src/index.d.ts" - }, - "repository": { - "directory": "packages-exp/installations-exp", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "devDependencies": { - "@firebase/app-exp": "0.0.900", - "rollup": "2.52.2", - "@rollup/plugin-commonjs": "17.1.0", - "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "11.2.0", - "rollup-plugin-typescript2": "0.30.0", - "rollup-plugin-uglify": "6.0.4", - "typescript": "4.2.2" - }, - "peerDependencies": { - "@firebase/app-exp": "0.x" - }, - "dependencies": { - "@firebase/util": "1.3.0", - "@firebase/component": "0.5.6", - "idb": "3.0.2", - "tslib": "^2.1.0" - }, - "esm5": "dist/index.esm.js" -} \ No newline at end of file diff --git a/packages-exp/installations-exp/rollup.config.js b/packages-exp/installations-exp/rollup.config.js deleted file mode 100644 index d66e464f546..00000000000 --- a/packages-exp/installations-exp/rollup.config.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [typescriptPlugin({ typescript }), json()]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/installations-exp/rollup.config.release.js b/packages-exp/installations-exp/rollup.config.release.js deleted file mode 100644 index 5050932f4a4..00000000000 --- a/packages-exp/installations-exp/rollup.config.release.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: false - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: false - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/installations-exp/rollup.shared.js b/packages-exp/installations-exp/rollup.shared.js deleted file mode 100644 index 329ad9eb73b..00000000000 --- a/packages-exp/installations-exp/rollup.shared.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import pkg from './package.json'; - -const deps = [ - ...Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }), - '@firebase/app' -]; - -/** - * ES5 Builds - */ -export const es5BuildsNoPlugin = [ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -export const es2017BuildsNoPlugin = [ - { - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/installations-exp/src/helpers/buffer-to-base64-url-safe.test.ts b/packages-exp/installations-exp/src/helpers/buffer-to-base64-url-safe.test.ts deleted file mode 100644 index 18e56f436f2..00000000000 --- a/packages-exp/installations-exp/src/helpers/buffer-to-base64-url-safe.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import '../testing/setup'; -import { bufferToBase64UrlSafe } from './buffer-to-base64-url-safe'; - -const str = 'hello world'; -const TYPED_ARRAY_REPRESENTATION = new Uint8Array(str.length); -for (let i = 0; i < str.length; i++) { - TYPED_ARRAY_REPRESENTATION[i] = str.charCodeAt(i); -} - -const BASE_64_REPRESENTATION = btoa(str); - -describe('bufferToBase64', () => { - it('returns a base64 representation of a Uint8Array', () => { - expect(bufferToBase64UrlSafe(TYPED_ARRAY_REPRESENTATION)).to.equal( - BASE_64_REPRESENTATION - ); - }); -}); diff --git a/packages-exp/installations-exp/src/helpers/buffer-to-base64-url-safe.ts b/packages-exp/installations-exp/src/helpers/buffer-to-base64-url-safe.ts deleted file mode 100644 index c336ce63528..00000000000 --- a/packages-exp/installations-exp/src/helpers/buffer-to-base64-url-safe.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export function bufferToBase64UrlSafe(array: Uint8Array): string { - const b64 = btoa(String.fromCharCode(...array)); - return b64.replace(/\+/g, '-').replace(/\//g, '_'); -} diff --git a/packages-exp/installations-exp/src/helpers/extract-app-config.test.ts b/packages-exp/installations-exp/src/helpers/extract-app-config.test.ts deleted file mode 100644 index 06a7ec2dcb6..00000000000 --- a/packages-exp/installations-exp/src/helpers/extract-app-config.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseError } from '@firebase/util'; -import { expect } from 'chai'; -import { AppConfig } from '../interfaces/installation-impl'; -import { getFakeApp } from '../testing/fake-generators'; -import '../testing/setup'; -import { extractAppConfig } from './extract-app-config'; - -describe('extractAppConfig', () => { - it('returns AppConfig if the argument is a FirebaseApp object that includes an appId', () => { - const firebaseApp = getFakeApp(); - const expected: AppConfig = { - appName: 'appName', - apiKey: 'apiKey', - projectId: 'projectId', - appId: '1:777777777777:web:d93b5ca1475efe57' - }; - expect(extractAppConfig(firebaseApp)).to.deep.equal(expected); - }); - - it('throws if a necessary value is missing', () => { - expect(() => extractAppConfig(undefined as any)).to.throw(FirebaseError); - - let firebaseApp = getFakeApp(); - delete (firebaseApp as any).name; - expect(() => extractAppConfig(firebaseApp)).to.throw(FirebaseError); - - firebaseApp = getFakeApp(); - delete (firebaseApp as any).options; - expect(() => extractAppConfig(firebaseApp)).to.throw(FirebaseError); - - firebaseApp = getFakeApp(); - delete firebaseApp.options.projectId; - expect(() => extractAppConfig(firebaseApp)).to.throw(FirebaseError); - - firebaseApp = getFakeApp(); - delete firebaseApp.options.apiKey; - expect(() => extractAppConfig(firebaseApp)).to.throw(FirebaseError); - - firebaseApp = getFakeApp(); - delete firebaseApp.options.appId; - expect(() => extractAppConfig(firebaseApp)).to.throw(FirebaseError); - }); -}); diff --git a/packages-exp/installations-exp/src/helpers/extract-app-config.ts b/packages-exp/installations-exp/src/helpers/extract-app-config.ts deleted file mode 100644 index b4c693f1b46..00000000000 --- a/packages-exp/installations-exp/src/helpers/extract-app-config.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp, FirebaseOptions } from '@firebase/app-exp'; -import { FirebaseError } from '@firebase/util'; -import { AppConfig } from '../interfaces/installation-impl'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; - -export function extractAppConfig(app: FirebaseApp): AppConfig { - if (!app || !app.options) { - throw getMissingValueError('App Configuration'); - } - - if (!app.name) { - throw getMissingValueError('App Name'); - } - - // Required app config keys - const configKeys: Array = [ - 'projectId', - 'apiKey', - 'appId' - ]; - - for (const keyName of configKeys) { - if (!app.options[keyName]) { - throw getMissingValueError(keyName); - } - } - - return { - appName: app.name, - projectId: app.options.projectId!, - apiKey: app.options.apiKey!, - appId: app.options.appId! - }; -} - -function getMissingValueError(valueName: string): FirebaseError { - return ERROR_FACTORY.create(ErrorCode.MISSING_APP_CONFIG_VALUES, { - valueName - }); -} diff --git a/packages-exp/installations-exp/src/helpers/fid-changed.test.ts b/packages-exp/installations-exp/src/helpers/fid-changed.test.ts deleted file mode 100644 index 458bc1d097d..00000000000 --- a/packages-exp/installations-exp/src/helpers/fid-changed.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { stub } from 'sinon'; -import '../testing/setup'; -import { AppConfig } from '../interfaces/installation-impl'; -import { - fidChanged, - addCallback, - removeCallback -} from '../helpers/fid-changed'; -import { getFakeAppConfig } from '../testing/fake-generators'; - -const FID = 'evil-lies-in-every-man'; - -describe('onIdChange', () => { - describe('with single app', () => { - let appConfig: AppConfig; - - beforeEach(() => { - appConfig = getFakeAppConfig(); - }); - - it('calls the provided callback when FID changes', () => { - const stubFn = stub(); - addCallback(appConfig, stubFn); - - fidChanged(appConfig, FID); - - expect(stubFn).to.have.been.calledOnceWith(FID); - }); - - it('calls multiple callbacks', () => { - const stubA = stub(); - addCallback(appConfig, stubA); - const stubB = stub(); - addCallback(appConfig, stubB); - - fidChanged(appConfig, FID); - - expect(stubA).to.have.been.calledOnceWith(FID); - expect(stubB).to.have.been.calledOnceWith(FID); - }); - - it('does not call removed callbacks', () => { - const stubFn = stub(); - addCallback(appConfig, stubFn); - - removeCallback(appConfig, stubFn); - fidChanged(appConfig, FID); - - expect(stubFn).not.to.have.been.called; - }); - - it('does not throw when removeCallback is called multiple times', () => { - const stubFn = stub(); - addCallback(appConfig, stubFn); - - removeCallback(appConfig, stubFn); - removeCallback(appConfig, stubFn); - fidChanged(appConfig, FID); - - expect(stubFn).not.to.have.been.called; - }); - }); - - describe('with multiple apps', () => { - let appConfigA: AppConfig; - let appConfigB: AppConfig; - - beforeEach(() => { - appConfigA = getFakeAppConfig(); - appConfigB = getFakeAppConfig({ appName: 'differentAppName' }); - }); - - it('calls the correct callback when FID changes', () => { - const stubA = stub(); - addCallback(appConfigA, stubA); - const stubB = stub(); - addCallback(appConfigB, stubB); - - fidChanged(appConfigA, FID); - - expect(stubA).to.have.been.calledOnceWith(FID); - expect(stubB).not.to.have.been.called; - }); - }); -}); diff --git a/packages-exp/installations-exp/src/helpers/fid-changed.ts b/packages-exp/installations-exp/src/helpers/fid-changed.ts deleted file mode 100644 index 63f04d75cea..00000000000 --- a/packages-exp/installations-exp/src/helpers/fid-changed.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getKey } from '../util/get-key'; -import { AppConfig } from '../interfaces/installation-impl'; -import { IdChangeCallbackFn } from '../api'; - -const fidChangeCallbacks: Map> = new Map(); - -/** - * Calls the onIdChange callbacks with the new FID value, and broadcasts the - * change to other tabs. - */ -export function fidChanged(appConfig: AppConfig, fid: string): void { - const key = getKey(appConfig); - - callFidChangeCallbacks(key, fid); - broadcastFidChange(key, fid); -} - -export function addCallback( - appConfig: AppConfig, - callback: IdChangeCallbackFn -): void { - // Open the broadcast channel if it's not already open, - // to be able to listen to change events from other tabs. - getBroadcastChannel(); - - const key = getKey(appConfig); - - let callbackSet = fidChangeCallbacks.get(key); - if (!callbackSet) { - callbackSet = new Set(); - fidChangeCallbacks.set(key, callbackSet); - } - callbackSet.add(callback); -} - -export function removeCallback( - appConfig: AppConfig, - callback: IdChangeCallbackFn -): void { - const key = getKey(appConfig); - - const callbackSet = fidChangeCallbacks.get(key); - - if (!callbackSet) { - return; - } - - callbackSet.delete(callback); - if (callbackSet.size === 0) { - fidChangeCallbacks.delete(key); - } - - // Close broadcast channel if there are no more callbacks. - closeBroadcastChannel(); -} - -function callFidChangeCallbacks(key: string, fid: string): void { - const callbacks = fidChangeCallbacks.get(key); - if (!callbacks) { - return; - } - - for (const callback of callbacks) { - callback(fid); - } -} - -function broadcastFidChange(key: string, fid: string): void { - const channel = getBroadcastChannel(); - if (channel) { - channel.postMessage({ key, fid }); - } - closeBroadcastChannel(); -} - -let broadcastChannel: BroadcastChannel | null = null; -/** Opens and returns a BroadcastChannel if it is supported by the browser. */ -function getBroadcastChannel(): BroadcastChannel | null { - if (!broadcastChannel && 'BroadcastChannel' in self) { - broadcastChannel = new BroadcastChannel('[Firebase] FID Change'); - broadcastChannel.onmessage = e => { - callFidChangeCallbacks(e.data.key, e.data.fid); - }; - } - return broadcastChannel; -} - -function closeBroadcastChannel(): void { - if (fidChangeCallbacks.size === 0 && broadcastChannel) { - broadcastChannel.close(); - broadcastChannel = null; - } -} diff --git a/packages-exp/installations-exp/src/helpers/generate-fid.test.ts b/packages-exp/installations-exp/src/helpers/generate-fid.test.ts deleted file mode 100644 index 5d5e3414d10..00000000000 --- a/packages-exp/installations-exp/src/helpers/generate-fid.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { stub } from 'sinon'; -import '../testing/setup'; -import { generateFid, VALID_FID_PATTERN } from './generate-fid'; - -/** A few random values to generate a FID from. */ -// prettier-ignore -const MOCK_RANDOM_VALUES = [ - [14, 107, 44, 183, 190, 84, 253, 45, 219, 233, 43, 190, 240, 152, 195, 222, 237], - [184, 251, 91, 157, 125, 225, 209, 15, 116, 66, 46, 113, 194, 126, 16, 13, 226], - [197, 123, 13, 142, 239, 129, 252, 139, 156, 36, 219, 192, 153, 52, 182, 231, 177], - [69, 154, 197, 91, 156, 196, 125, 111, 3, 67, 212, 132, 169, 11, 14, 254, 125], - [193, 102, 58, 19, 244, 69, 36, 135, 170, 106, 98, 216, 246, 209, 24, 155, 149], - [252, 59, 222, 160, 82, 160, 82, 186, 14, 172, 196, 114, 146, 191, 196, 194, 146], - [64, 147, 153, 236, 225, 142, 235, 109, 184, 249, 174, 127, 33, 238, 227, 172, 111], - [129, 137, 136, 120, 248, 206, 253, 78, 159, 201, 216, 15, 246, 80, 118, 185, 211], - [117, 150, 2, 180, 116, 230, 45, 188, 183, 43, 152, 100, 50, 255, 101, 175, 190], - [156, 129, 30, 101, 58, 137, 217, 249, 12, 227, 235, 80, 248, 81, 191, 2, 5], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], -]; - -/** The FIDs that should be generated based on MOCK_RANDOM_VALUES. */ -const EXPECTED_FIDS = [ - 'fmsst75U_S3b6Su-8JjD3u', - 'ePtbnX3h0Q90Qi5xwn4QDe', - 'dXsNju-B_IucJNvAmTS257', - 'dZrFW5zEfW8DQ9SEqQsO_n', - 'cWY6E_RFJIeqamLY9tEYm5', - 'fDveoFKgUroOrMRykr_Ewp', - 'cJOZ7OGO6224-a5_Ie7jrG', - 'cYmIePjO_U6fydgP9lB2ud', - 'dZYCtHTmLby3K5hkMv9lr7', - 'fIEeZTqJ2fkM4-tQ-FG_Ag', - 'cAAAAAAAAAAAAAAAAAAAAA', - 'f_____________________' -]; - -describe('generateFid', () => { - it('deterministically generates FIDs based on crypto.getRandomValues', () => { - let randomValueIndex = 0; - stub(crypto, 'getRandomValues').callsFake(array => { - if (!(array instanceof Uint8Array)) { - throw new Error('what'); - } - const values = MOCK_RANDOM_VALUES[randomValueIndex++]; - for (let i = 0; i < array.length; i++) { - array[i] = values[i]; - } - return array; - }); - - for (const expectedFid of EXPECTED_FIDS) { - expect(generateFid()).to.deep.equal(expectedFid); - } - }); - - it('generates valid FIDs', () => { - for (let i = 0; i < 1000; i++) { - const fid = generateFid(); - expect(VALID_FID_PATTERN.test(fid)).to.equal( - true, - `${fid} is not a valid FID` - ); - } - }); - - it('generates FIDs where each character is equally likely to appear in each location', () => { - const numTries = 200000; - - const charOccurrencesMapList: Array> = new Array(22); - for (let i = 0; i < charOccurrencesMapList.length; i++) { - charOccurrencesMapList[i] = new Map(); - } - - for (let i = 0; i < numTries; i++) { - const fid = generateFid(); - - Array.from(fid).forEach((char, location) => { - const map = charOccurrencesMapList[location]; - map.set(char, (map.get(char) || 0) + 1); - }); - } - - for (let i = 0; i < charOccurrencesMapList.length; i++) { - const map = charOccurrencesMapList[i]; - if (i === 0) { - // In the first location only 4 characters (c, d, e, f) are valid. - expect(map.size).to.equal(4); - } else { - // In locations other than the first, all 64 characters are valid. - expect(map.size).to.equal(64); - } - - Array.from(map.entries()).forEach(([_, occurrence]) => { - const expectedOccurrence = numTries / map.size; - - // 10% margin of error - expect(occurrence).to.be.above(expectedOccurrence * 0.9); - expect(occurrence).to.be.below(expectedOccurrence * 1.1); - }); - } - }).timeout(30000); - - it('returns an empty string if FID generation fails', () => { - stub(crypto, 'getRandomValues').throws(); - - const fid = generateFid(); - expect(fid).to.equal(''); - }); -}); diff --git a/packages-exp/installations-exp/src/helpers/generate-fid.ts b/packages-exp/installations-exp/src/helpers/generate-fid.ts deleted file mode 100644 index 5d87df04628..00000000000 --- a/packages-exp/installations-exp/src/helpers/generate-fid.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { bufferToBase64UrlSafe } from './buffer-to-base64-url-safe'; - -export const VALID_FID_PATTERN = /^[cdef][\w-]{21}$/; -export const INVALID_FID = ''; - -/** - * Generates a new FID using random values from Web Crypto API. - * Returns an empty string if FID generation fails for any reason. - */ -export function generateFid(): string { - try { - // A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 - // bytes. our implementation generates a 17 byte array instead. - const fidByteArray = new Uint8Array(17); - const crypto = - self.crypto || ((self as unknown) as { msCrypto: Crypto }).msCrypto; - crypto.getRandomValues(fidByteArray); - - // Replace the first 4 random bits with the constant FID header of 0b0111. - fidByteArray[0] = 0b01110000 + (fidByteArray[0] % 0b00010000); - - const fid = encode(fidByteArray); - - return VALID_FID_PATTERN.test(fid) ? fid : INVALID_FID; - } catch { - // FID generation errored - return INVALID_FID; - } -} - -/** Converts a FID Uint8Array to a base64 string representation. */ -function encode(fidByteArray: Uint8Array): string { - const b64String = bufferToBase64UrlSafe(fidByteArray); - - // Remove the 23rd character that was added because of the extra 4 bits at the - // end of our 17 byte array, and the '=' padding. - return b64String.substr(0, 22); -} diff --git a/packages-exp/installations-exp/src/helpers/get-installation-entry.test.ts b/packages-exp/installations-exp/src/helpers/get-installation-entry.test.ts deleted file mode 100644 index b19ec6ea037..00000000000 --- a/packages-exp/installations-exp/src/helpers/get-installation-entry.test.ts +++ /dev/null @@ -1,477 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AssertionError, expect } from 'chai'; -import { SinonFakeTimers, SinonStub, stub, useFakeTimers } from 'sinon'; -import * as createInstallationRequestModule from '../functions/create-installation-request'; -import { AppConfig } from '../interfaces/installation-impl'; -import { - InProgressInstallationEntry, - RegisteredInstallationEntry, - RequestStatus, - UnregisteredInstallationEntry -} from '../interfaces/installation-entry'; -import { getFakeAppConfig } from '../testing/fake-generators'; -import '../testing/setup'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { sleep } from '../util/sleep'; -import * as generateFidModule from './generate-fid'; -import { getInstallationEntry } from './get-installation-entry'; -import { get, set } from './idb-manager'; - -const FID = 'cry-of-the-black-birds'; - -describe('getInstallationEntry', () => { - let clock: SinonFakeTimers; - let appConfig: AppConfig; - let createInstallationRequestSpy: SinonStub< - [AppConfig, InProgressInstallationEntry], - Promise - >; - - beforeEach(() => { - clock = useFakeTimers({ now: 1_000_000 }); - appConfig = getFakeAppConfig(); - createInstallationRequestSpy = stub( - createInstallationRequestModule, - 'createInstallationRequest' - ).callsFake( - async (_, installationEntry): Promise => { - await sleep(500); // Request would take some time - const registeredInstallationEntry: RegisteredInstallationEntry = { - // Returns new FID if client FID is invalid. - fid: installationEntry.fid || FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now(), - token: 'token', - expiresIn: 1_000_000_000 - } - }; - return registeredInstallationEntry; - } - ); - }); - - afterEach(() => { - // Clean up all pending requests. - clock.runAll(); - }); - - it('saves the InstallationEntry in the database before returning it', async () => { - const oldDbEntry = await get(appConfig); - expect(oldDbEntry).to.be.undefined; - - const { installationEntry } = await getInstallationEntry(appConfig); - - const newDbEntry = await get(appConfig); - expect(newDbEntry).to.deep.equal(installationEntry); - }); - - it('saves the InstallationEntry in the database if app is offline', async () => { - stub(navigator, 'onLine').value(false); - - const oldDbEntry = await get(appConfig); - expect(oldDbEntry).to.be.undefined; - - const { installationEntry } = await getInstallationEntry(appConfig); - - const newDbEntry = await get(appConfig); - expect(newDbEntry).to.deep.equal(installationEntry); - }); - - it('saves the InstallationEntry in the database when registration completes', async () => { - const { - installationEntry, - registrationPromise - } = await getInstallationEntry(appConfig); - expect(installationEntry.registrationStatus).to.equal( - RequestStatus.IN_PROGRESS - ); - expect(registrationPromise).to.be.an.instanceOf(Promise); - - const oldDbEntry = await get(appConfig); - expect(oldDbEntry).to.deep.equal(installationEntry); - - clock.next(); // Finish registration request. - await expect(registrationPromise).to.be.fulfilled; - - const newDbEntry = await get(appConfig); - expect(newDbEntry!.registrationStatus).to.equal(RequestStatus.COMPLETED); - }); - - it('saves the InstallationEntry in the database when registration fails', async () => { - createInstallationRequestSpy.callsFake(async () => { - await sleep(500); // Request would take some time - throw ERROR_FACTORY.create(ErrorCode.REQUEST_FAILED, { - requestName: 'Create Installation', - serverCode: 500, - serverStatus: 'INTERNAL', - serverMessage: 'Internal server error.' - }); - }); - - const { - installationEntry, - registrationPromise - } = await getInstallationEntry(appConfig); - expect(installationEntry.registrationStatus).to.equal( - RequestStatus.IN_PROGRESS - ); - expect(registrationPromise).to.be.an.instanceOf(Promise); - - const oldDbEntry = await get(appConfig); - expect(oldDbEntry).to.deep.equal(installationEntry); - - clock.next(); // Finish registration request. - await expect(registrationPromise).to.be.rejected; - - const newDbEntry = await get(appConfig); - expect(newDbEntry!.registrationStatus).to.equal(RequestStatus.NOT_STARTED); - }); - - it('removes the InstallationEntry from the database when registration fails with 409', async () => { - createInstallationRequestSpy.callsFake(async () => { - await sleep(500); // Request would take some time - throw ERROR_FACTORY.create(ErrorCode.REQUEST_FAILED, { - requestName: 'Create Installation', - serverCode: 409, - serverStatus: 'INVALID_ARGUMENT', - serverMessage: 'FID can not be used.' - }); - }); - - const { - installationEntry, - registrationPromise - } = await getInstallationEntry(appConfig); - expect(installationEntry.registrationStatus).to.equal( - RequestStatus.IN_PROGRESS - ); - - const oldDbEntry = await get(appConfig); - expect(oldDbEntry).to.deep.equal(installationEntry); - - clock.next(); // Finish registration request. - await expect(registrationPromise).to.be.rejected; - - const newDbEntry = await get(appConfig); - expect(newDbEntry).to.be.undefined; - }); - - it('returns the same FID on subsequent calls', async () => { - const { installationEntry: entry1 } = await getInstallationEntry(appConfig); - const { installationEntry: entry2 } = await getInstallationEntry(appConfig); - expect(entry1.fid).to.equal(entry2.fid); - }); - - describe('when there is no InstallationEntry in database', () => { - let generateInstallationEntrySpy: SinonStub<[], string>; - - beforeEach(() => { - generateInstallationEntrySpy = stub( - generateFidModule, - 'generateFid' - ).returns(FID); - }); - - it('returns a new pending InstallationEntry and triggers createInstallation', async () => { - const { - installationEntry, - registrationPromise - } = await getInstallationEntry(appConfig); - - if (installationEntry.registrationStatus !== RequestStatus.IN_PROGRESS) { - throw new AssertionError('InstallationEntry is not IN_PROGRESS.'); - } - - expect(registrationPromise).to.be.an.instanceOf(Promise); - expect(installationEntry).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - - // https://github.com/chaijs/chai/issues/644 - registrationTime: installationEntry.registrationTime - }); - expect(generateInstallationEntrySpy).to.be.called; - expect(createInstallationRequestSpy).to.be.called; - }); - - it('returns a new unregistered InstallationEntry if app is offline', async () => { - stub(navigator, 'onLine').value(false); - - const { installationEntry } = await getInstallationEntry(appConfig); - - expect(installationEntry).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }); - expect(generateInstallationEntrySpy).to.be.called; - expect(createInstallationRequestSpy).not.to.be.called; - }); - - it('does not trigger createInstallation REST call on subsequent calls', async () => { - await getInstallationEntry(appConfig); - await getInstallationEntry(appConfig); - - expect(createInstallationRequestSpy).to.be.calledOnce; - }); - - it('returns a registrationPromise on subsequent calls before initial promise resolves', async () => { - const { registrationPromise: promise1 } = await getInstallationEntry( - appConfig - ); - const { registrationPromise: promise2 } = await getInstallationEntry( - appConfig - ); - - expect(createInstallationRequestSpy).to.be.calledOnce; - expect(promise1).to.be.an.instanceOf(Promise); - expect(promise2).to.be.an.instanceOf(Promise); - }); - - it('does not return a registrationPromise on subsequent calls after initial promise resolves', async () => { - const { registrationPromise: promise1 } = await getInstallationEntry( - appConfig - ); - expect(promise1).to.be.an.instanceOf(Promise); - - clock.next(); // Finish registration request. - await expect(promise1).to.be.fulfilled; - - const { registrationPromise: promise2 } = await getInstallationEntry( - appConfig - ); - expect(promise2).to.be.undefined; - - expect(createInstallationRequestSpy).to.be.calledOnce; - }); - - it('waits for the FID from the server if FID generation fails', async () => { - clock.restore(); - clock = useFakeTimers({ - now: 1_000_000, - shouldAdvanceTime: true /* Needed to allow the createInstallation request to complete. */ - }); - - // FID generation fails. - generateInstallationEntrySpy.returns(generateFidModule.INVALID_FID); - - const getInstallationEntryPromise = getInstallationEntry(appConfig); - - const { - installationEntry, - registrationPromise - } = await getInstallationEntryPromise; - - expect(installationEntry.fid).to.equal(FID); - expect(registrationPromise).to.be.undefined; - }); - }); - - describe('when there is an unregistered InstallationEntry in the database', () => { - beforeEach(async () => { - const unregisteredInstallationEntry: UnregisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }; - await set(appConfig, unregisteredInstallationEntry); - }); - - it('returns a pending InstallationEntry and triggers createInstallation', async () => { - const { - installationEntry, - registrationPromise - } = await getInstallationEntry(appConfig); - - if (installationEntry.registrationStatus !== RequestStatus.IN_PROGRESS) { - throw new AssertionError('InstallationEntry is not IN_PROGRESS.'); - } - - expect(registrationPromise).to.be.an.instanceOf(Promise); - expect(installationEntry).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - // https://github.com/chaijs/chai/issues/644 - registrationTime: installationEntry.registrationTime - }); - expect(createInstallationRequestSpy).to.be.calledOnce; - }); - - it('returns the same InstallationEntry if the app is offline', async () => { - stub(navigator, 'onLine').value(false); - - const { installationEntry } = await getInstallationEntry(appConfig); - - expect(installationEntry).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }); - expect(createInstallationRequestSpy).not.to.be.called; - }); - }); - - describe('when there is a pending InstallationEntry in the database', () => { - beforeEach(async () => { - const inProgressInstallationEntry: InProgressInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - registrationTime: 1_000_000 - }; - await set(appConfig, inProgressInstallationEntry); - }); - - it("returns the same InstallationEntry if the request hasn't timed out", async () => { - clock.now = 1_001_000; // One second after the request was initiated. - - const { installationEntry } = await getInstallationEntry(appConfig); - - expect(installationEntry).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - registrationTime: 1_000_000 - }); - expect(createInstallationRequestSpy).not.to.be.called; - }); - - it('updates the InstallationEntry and triggers createInstallation if the request fails', async () => { - clock.restore(); - clock = useFakeTimers({ - now: 1_001_000 /* One second after the request was initiated. */, - shouldAdvanceTime: true /* Needed to allow the createInstallation request to complete. */ - }); - - const installationEntryPromise = getInstallationEntry(appConfig); - - // The pending request fails after a while. - clock.tick(3000); - await set(appConfig, { - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }); - - const { registrationPromise } = await installationEntryPromise; - - // Let the new getInstallationEntry process start. - await sleep(250); - - const tokenDetails = (await get( - appConfig - )) as InProgressInstallationEntry; - expect(tokenDetails.registrationTime).to.be.at.least( - /* When the first pending request failed. */ 1_004_000 - ); - expect(tokenDetails).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - // Ignore registrationTime as we already checked it. - registrationTime: tokenDetails.registrationTime - }); - - expect(registrationPromise).to.be.an.instanceOf(Promise); - await registrationPromise; - expect(createInstallationRequestSpy).to.be.calledOnce; - }); - - it('updates the InstallationEntry if the request fails and the app is offline', async () => { - stub(navigator, 'onLine').value(false); - - clock.restore(); - clock = useFakeTimers({ - now: 1_001_000 /* One second after the request was initiated. */, - shouldAdvanceTime: true /* Needed to allow the createInstallation request to complete. */ - }); - - const installationEntryPromise = getInstallationEntry(appConfig); - - // The pending request fails after a while. - clock.tick(3000); - await set(appConfig, { - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }); - - const { registrationPromise } = await installationEntryPromise; - - // Let the new getInstallationEntry process start. - await sleep(250); - - expect(await get(appConfig)).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }); - - expect(registrationPromise).to.be.an.instanceOf(Promise); - await expect(registrationPromise).to.be.rejectedWith( - 'Application offline' - ); - expect(createInstallationRequestSpy).not.to.be.called; - }); - - it('returns a new pending InstallationEntry and triggers createInstallation if the request had already timed out', async () => { - clock.now = 1_015_000; // Fifteen seconds after the request was initiated. - - const { installationEntry } = await getInstallationEntry(appConfig); - - expect(installationEntry).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - registrationTime: 1_015_000 - }); - expect(createInstallationRequestSpy).to.be.calledOnce; - }); - - it('returns a new unregistered InstallationEntry if the request had already timed out and the app is offline', async () => { - stub(navigator, 'onLine').value(false); - clock.now = 1_015_000; // Fifteen seconds after the request was initiated. - - const { installationEntry } = await getInstallationEntry(appConfig); - - expect(installationEntry).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }); - expect(createInstallationRequestSpy).not.to.be.called; - }); - }); - - describe('when there is a registered InstallationEntry in the database', () => { - beforeEach(async () => { - const registeredInstallationEntry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { requestStatus: RequestStatus.NOT_STARTED } - }; - await set(appConfig, registeredInstallationEntry); - }); - - it('returns the InstallationEntry from the database', async () => { - const { installationEntry } = await getInstallationEntry(appConfig); - - expect(installationEntry).to.deep.equal({ - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { requestStatus: RequestStatus.NOT_STARTED } - }); - expect(createInstallationRequestSpy).not.to.be.called; - }); - }); -}); diff --git a/packages-exp/installations-exp/src/helpers/get-installation-entry.ts b/packages-exp/installations-exp/src/helpers/get-installation-entry.ts deleted file mode 100644 index 6b57563eb80..00000000000 --- a/packages-exp/installations-exp/src/helpers/get-installation-entry.ts +++ /dev/null @@ -1,227 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createInstallationRequest } from '../functions/create-installation-request'; -import { AppConfig } from '../interfaces/installation-impl'; -import { - InProgressInstallationEntry, - InstallationEntry, - RegisteredInstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { PENDING_TIMEOUT_MS } from '../util/constants'; -import { ERROR_FACTORY, ErrorCode, isServerError } from '../util/errors'; -import { sleep } from '../util/sleep'; -import { generateFid, INVALID_FID } from './generate-fid'; -import { remove, set, update } from './idb-manager'; - -export interface InstallationEntryWithRegistrationPromise { - installationEntry: InstallationEntry; - /** Exist iff the installationEntry is not registered. */ - registrationPromise?: Promise; -} - -/** - * Updates and returns the InstallationEntry from the database. - * Also triggers a registration request if it is necessary and possible. - */ -export async function getInstallationEntry( - appConfig: AppConfig -): Promise { - let registrationPromise: Promise | undefined; - - const installationEntry = await update(appConfig, oldEntry => { - const installationEntry = updateOrCreateInstallationEntry(oldEntry); - const entryWithPromise = triggerRegistrationIfNecessary( - appConfig, - installationEntry - ); - registrationPromise = entryWithPromise.registrationPromise; - return entryWithPromise.installationEntry; - }); - - if (installationEntry.fid === INVALID_FID) { - // FID generation failed. Waiting for the FID from the server. - return { installationEntry: await registrationPromise! }; - } - - return { - installationEntry, - registrationPromise - }; -} - -/** - * Creates a new Installation Entry if one does not exist. - * Also clears timed out pending requests. - */ -function updateOrCreateInstallationEntry( - oldEntry: InstallationEntry | undefined -): InstallationEntry { - const entry: InstallationEntry = oldEntry || { - fid: generateFid(), - registrationStatus: RequestStatus.NOT_STARTED - }; - - return clearTimedOutRequest(entry); -} - -/** - * If the Firebase Installation is not registered yet, this will trigger the - * registration and return an InProgressInstallationEntry. - * - * If registrationPromise does not exist, the installationEntry is guaranteed - * to be registered. - */ -function triggerRegistrationIfNecessary( - appConfig: AppConfig, - installationEntry: InstallationEntry -): InstallationEntryWithRegistrationPromise { - if (installationEntry.registrationStatus === RequestStatus.NOT_STARTED) { - if (!navigator.onLine) { - // Registration required but app is offline. - const registrationPromiseWithError = Promise.reject( - ERROR_FACTORY.create(ErrorCode.APP_OFFLINE) - ); - return { - installationEntry, - registrationPromise: registrationPromiseWithError - }; - } - - // Try registering. Change status to IN_PROGRESS. - const inProgressEntry: InProgressInstallationEntry = { - fid: installationEntry.fid, - registrationStatus: RequestStatus.IN_PROGRESS, - registrationTime: Date.now() - }; - const registrationPromise = registerInstallation( - appConfig, - inProgressEntry - ); - return { installationEntry: inProgressEntry, registrationPromise }; - } else if ( - installationEntry.registrationStatus === RequestStatus.IN_PROGRESS - ) { - return { - installationEntry, - registrationPromise: waitUntilFidRegistration(appConfig) - }; - } else { - return { installationEntry }; - } -} - -/** This will be executed only once for each new Firebase Installation. */ -async function registerInstallation( - appConfig: AppConfig, - installationEntry: InProgressInstallationEntry -): Promise { - try { - const registeredInstallationEntry = await createInstallationRequest( - appConfig, - installationEntry - ); - return set(appConfig, registeredInstallationEntry); - } catch (e) { - if (isServerError(e) && e.customData.serverCode === 409) { - // Server returned a "FID can not be used" error. - // Generate a new ID next time. - await remove(appConfig); - } else { - // Registration failed. Set FID as not registered. - await set(appConfig, { - fid: installationEntry.fid, - registrationStatus: RequestStatus.NOT_STARTED - }); - } - throw e; - } -} - -/** Call if FID registration is pending in another request. */ -async function waitUntilFidRegistration( - appConfig: AppConfig -): Promise { - // Unfortunately, there is no way of reliably observing when a value in - // IndexedDB changes (yet, see https://github.com/WICG/indexed-db-observers), - // so we need to poll. - - let entry: InstallationEntry = await updateInstallationRequest(appConfig); - while (entry.registrationStatus === RequestStatus.IN_PROGRESS) { - // createInstallation request still in progress. - await sleep(100); - - entry = await updateInstallationRequest(appConfig); - } - - if (entry.registrationStatus === RequestStatus.NOT_STARTED) { - // The request timed out or failed in a different call. Try again. - const { - installationEntry, - registrationPromise - } = await getInstallationEntry(appConfig); - - if (registrationPromise) { - return registrationPromise; - } else { - // if there is no registrationPromise, entry is registered. - return installationEntry as RegisteredInstallationEntry; - } - } - - return entry; -} - -/** - * Called only if there is a CreateInstallation request in progress. - * - * Updates the InstallationEntry in the DB based on the status of the - * CreateInstallation request. - * - * Returns the updated InstallationEntry. - */ -function updateInstallationRequest( - appConfig: AppConfig -): Promise { - return update(appConfig, oldEntry => { - if (!oldEntry) { - throw ERROR_FACTORY.create(ErrorCode.INSTALLATION_NOT_FOUND); - } - return clearTimedOutRequest(oldEntry); - }); -} - -function clearTimedOutRequest(entry: InstallationEntry): InstallationEntry { - if (hasInstallationRequestTimedOut(entry)) { - return { - fid: entry.fid, - registrationStatus: RequestStatus.NOT_STARTED - }; - } - - return entry; -} - -function hasInstallationRequestTimedOut( - installationEntry: InstallationEntry -): boolean { - return ( - installationEntry.registrationStatus === RequestStatus.IN_PROGRESS && - installationEntry.registrationTime + PENDING_TIMEOUT_MS < Date.now() - ); -} diff --git a/packages-exp/installations-exp/src/helpers/idb-manager.test.ts b/packages-exp/installations-exp/src/helpers/idb-manager.test.ts deleted file mode 100644 index db7eaca58f9..00000000000 --- a/packages-exp/installations-exp/src/helpers/idb-manager.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { stub } from 'sinon'; -import { AppConfig } from '../interfaces/installation-impl'; -import { - InstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { getFakeAppConfig } from '../testing/fake-generators'; -import '../testing/setup'; -import { clear, get, remove, set, update } from './idb-manager'; -import * as fidChangedModule from './fid-changed'; - -const VALUE_A: InstallationEntry = { - fid: 'VALUE_A', - registrationStatus: RequestStatus.NOT_STARTED -}; -const VALUE_B: InstallationEntry = { - fid: 'VALUE_B', - registrationStatus: RequestStatus.NOT_STARTED -}; - -describe('idb manager', () => { - let appConfig: AppConfig; - - beforeEach(() => { - appConfig = { ...getFakeAppConfig(), appName: 'appName1' }; - }); - - describe('get / set', () => { - it('sets a value and then gets the same value back', async () => { - await set(appConfig, VALUE_A); - const value = await get(appConfig); - expect(value).to.deep.equal(VALUE_A); - }); - - it('gets undefined for a key that does not exist', async () => { - const value = await get(appConfig); - expect(value).to.be.undefined; - }); - - it('sets and gets multiple values with different keys', async () => { - const appConfig2: AppConfig = { - ...getFakeAppConfig(), - appName: 'appName2' - }; - - await set(appConfig, VALUE_A); - await set(appConfig2, VALUE_B); - expect(await get(appConfig)).to.deep.equal(VALUE_A); - expect(await get(appConfig2)).to.deep.equal(VALUE_B); - }); - - it('overwrites a value', async () => { - await set(appConfig, VALUE_A); - await set(appConfig, VALUE_B); - expect(await get(appConfig)).to.deep.equal(VALUE_B); - }); - - it('calls fidChanged when a new FID is generated', async () => { - const fidChangedStub = stub(fidChangedModule, 'fidChanged'); - await set(appConfig, VALUE_A); - - expect(fidChangedStub).to.have.been.calledOnceWith( - appConfig, - VALUE_A.fid - ); - }); - - it('calls fidChanged when the FID changes', async () => { - await set(appConfig, VALUE_A); - - const fidChangedStub = stub(fidChangedModule, 'fidChanged'); - await set(appConfig, VALUE_B); - - expect(fidChangedStub).to.have.been.calledOnceWith( - appConfig, - VALUE_B.fid - ); - }); - - it('does not call fidChanged when the FID is the same', async () => { - await set(appConfig, VALUE_A); - - const fidChangedStub = stub(fidChangedModule, 'fidChanged'); - await set(appConfig, /* Same value */ VALUE_A); - - expect(fidChangedStub).not.to.have.been.called; - }); - }); - - describe('remove', () => { - it('deletes a key', async () => { - await set(appConfig, VALUE_A); - await remove(appConfig); - expect(await get(appConfig)).to.be.undefined; - }); - - it('does not throw if key does not exist', async () => { - await remove(appConfig); - expect(await get(appConfig)).to.be.undefined; - }); - }); - - describe('clear', () => { - it('deletes all keys', async () => { - const appConfig2: AppConfig = { - ...getFakeAppConfig(), - appName: 'appName2' - }; - - await set(appConfig, VALUE_A); - await set(appConfig2, VALUE_B); - await clear(); - expect(await get(appConfig)).to.be.undefined; - expect(await get(appConfig2)).to.be.undefined; - }); - }); - - describe('update', () => { - it('gets and sets a value atomically, returns the new value', async () => { - let isGetCalled = false; - - await set(appConfig, VALUE_A); - - const resultPromise = update(appConfig, oldValue => { - // get is already called for the same key, but it will only complete - // after update transaction finishes, at which point it will return the - // new value. - expect(isGetCalled).to.be.true; - - expect(oldValue).to.deep.equal(VALUE_A); - return VALUE_B; - }); - - // Called immediately after update, but before update completed. - const getPromise = get(appConfig); - isGetCalled = true; - - // Update returns the new value - expect(await resultPromise).to.deep.equal(VALUE_B); - - // If update weren't atomic, this would return the old value. - expect(await getPromise).to.deep.equal(VALUE_B); - }); - - it('calls fidChanged when a new FID is generated', async () => { - const fidChangedStub = stub(fidChangedModule, 'fidChanged'); - await update(appConfig, () => VALUE_A); - - expect(fidChangedStub).to.have.been.calledOnceWith( - appConfig, - VALUE_A.fid - ); - }); - - it('calls fidChanged when the FID changes', async () => { - await set(appConfig, VALUE_A); - - const fidChangedStub = stub(fidChangedModule, 'fidChanged'); - await update(appConfig, () => VALUE_B); - - expect(fidChangedStub).to.have.been.calledOnceWith( - appConfig, - VALUE_B.fid - ); - }); - - it('does not call fidChanged when the FID is the same', async () => { - await set(appConfig, VALUE_A); - - const fidChangedStub = stub(fidChangedModule, 'fidChanged'); - await update(appConfig, () => /* Same value */ VALUE_A); - - expect(fidChangedStub).not.to.have.been.called; - }); - }); -}); diff --git a/packages-exp/installations-exp/src/helpers/idb-manager.ts b/packages-exp/installations-exp/src/helpers/idb-manager.ts deleted file mode 100644 index bc30563fa06..00000000000 --- a/packages-exp/installations-exp/src/helpers/idb-manager.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DB, openDb } from 'idb'; -import { AppConfig } from '../interfaces/installation-impl'; -import { InstallationEntry } from '../interfaces/installation-entry'; -import { getKey } from '../util/get-key'; -import { fidChanged } from './fid-changed'; - -const DATABASE_NAME = 'firebase-installations-database'; -const DATABASE_VERSION = 1; -const OBJECT_STORE_NAME = 'firebase-installations-store'; - -let dbPromise: Promise | null = null; -function getDbPromise(): Promise { - if (!dbPromise) { - dbPromise = openDb(DATABASE_NAME, DATABASE_VERSION, upgradeDB => { - // We don't use 'break' in this switch statement, the fall-through - // behavior is what we want, because if there are multiple versions between - // the old version and the current version, we want ALL the migrations - // that correspond to those versions to run, not only the last one. - // eslint-disable-next-line default-case - switch (upgradeDB.oldVersion) { - case 0: - upgradeDB.createObjectStore(OBJECT_STORE_NAME); - } - }); - } - return dbPromise; -} - -/** Gets record(s) from the objectStore that match the given key. */ -export async function get( - appConfig: AppConfig -): Promise { - const key = getKey(appConfig); - const db = await getDbPromise(); - return db - .transaction(OBJECT_STORE_NAME) - .objectStore(OBJECT_STORE_NAME) - .get(key); -} - -/** Assigns or overwrites the record for the given key with the given value. */ -export async function set( - appConfig: AppConfig, - value: ValueType -): Promise { - const key = getKey(appConfig); - const db = await getDbPromise(); - const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); - const objectStore = tx.objectStore(OBJECT_STORE_NAME); - const oldValue = await objectStore.get(key); - await objectStore.put(value, key); - await tx.complete; - - if (!oldValue || oldValue.fid !== value.fid) { - fidChanged(appConfig, value.fid); - } - - return value; -} - -/** Removes record(s) from the objectStore that match the given key. */ -export async function remove(appConfig: AppConfig): Promise { - const key = getKey(appConfig); - const db = await getDbPromise(); - const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); - await tx.objectStore(OBJECT_STORE_NAME).delete(key); - await tx.complete; -} - -/** - * Atomically updates a record with the result of updateFn, which gets - * called with the current value. If newValue is undefined, the record is - * deleted instead. - * @return Updated value - */ -export async function update( - appConfig: AppConfig, - updateFn: (previousValue: InstallationEntry | undefined) => ValueType -): Promise { - const key = getKey(appConfig); - const db = await getDbPromise(); - const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); - const store = tx.objectStore(OBJECT_STORE_NAME); - const oldValue: InstallationEntry | undefined = await store.get(key); - const newValue = updateFn(oldValue); - - if (newValue === undefined) { - await store.delete(key); - } else { - await store.put(newValue, key); - } - await tx.complete; - - if (newValue && (!oldValue || oldValue.fid !== newValue.fid)) { - fidChanged(appConfig, newValue.fid); - } - - return newValue; -} - -export async function clear(): Promise { - const db = await getDbPromise(); - const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); - await tx.objectStore(OBJECT_STORE_NAME).clear(); - await tx.complete; -} diff --git a/packages-exp/installations-exp/src/helpers/refresh-auth-token.test.ts b/packages-exp/installations-exp/src/helpers/refresh-auth-token.test.ts deleted file mode 100644 index 3bdd859a6b0..00000000000 --- a/packages-exp/installations-exp/src/helpers/refresh-auth-token.test.ts +++ /dev/null @@ -1,208 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonFakeTimers, SinonStub, stub, useFakeTimers } from 'sinon'; -import * as generateAuthTokenRequestModule from '../functions/generate-auth-token-request'; -import { - CompletedAuthToken, - RegisteredInstallationEntry, - RequestStatus, - UnregisteredInstallationEntry -} from '../interfaces/installation-entry'; -import { getFakeInstallations } from '../testing/fake-generators'; -import '../testing/setup'; -import { TOKEN_EXPIRATION_BUFFER } from '../util/constants'; -import { sleep } from '../util/sleep'; -import { get, set } from './idb-manager'; -import { refreshAuthToken } from './refresh-auth-token'; -import { FirebaseInstallationsImpl } from '../interfaces/installation-impl'; - -const FID = 'carry-the-blessed-home'; -const AUTH_TOKEN = 'authTokenFromServer'; -const DB_AUTH_TOKEN = 'authTokenFromDB'; -const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000; - -describe('refreshAuthToken', () => { - let installations: FirebaseInstallationsImpl; - let generateAuthTokenRequestSpy: SinonStub< - [FirebaseInstallationsImpl, RegisteredInstallationEntry], - Promise - >; - - beforeEach(() => { - installations = getFakeInstallations(); - - generateAuthTokenRequestSpy = stub( - generateAuthTokenRequestModule, - 'generateAuthTokenRequest' - ).callsFake(async () => { - await sleep(100); // Request would take some time - const result: CompletedAuthToken = { - token: AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - }; - return result; - }); - }); - - it('throws when there is no installation in the DB', async () => { - await expect(refreshAuthToken(installations)).to.be.rejected; - }); - - it('throws when there is an unregistered installation in the db', async () => { - const installationEntry: UnregisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }; - await set(installations.appConfig, installationEntry); - - await expect(refreshAuthToken(installations)).to.be.rejected; - }); - - describe('when there is a valid auth token in the DB', () => { - beforeEach(async () => { - const installationEntry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - } - }; - await set(installations.appConfig, installationEntry); - }); - - it('returns the token from the DB', async () => { - const { token } = await refreshAuthToken(installations); - expect(token).to.equal(AUTH_TOKEN); - }); - - it('does not call any server APIs', async () => { - await refreshAuthToken(installations); - expect(generateAuthTokenRequestSpy).not.to.be.called; - }); - - it('works even if the app is offline', async () => { - stub(navigator, 'onLine').value(false); - - const { token } = await refreshAuthToken(installations); - expect(token).to.equal(AUTH_TOKEN); - }); - }); - - describe('when there is an auth token that is about to expire in the DB', () => { - let clock: SinonFakeTimers; - - beforeEach(async () => { - clock = useFakeTimers({ shouldAdvanceTime: true }); - - const installationEntry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: DB_AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: - // Expires in ten minutes - Date.now() - ONE_WEEK_MS + TOKEN_EXPIRATION_BUFFER + 10 * 60 * 1000 - } - }; - await set(installations.appConfig, installationEntry); - }); - - it('returns a different token after expiration', async () => { - const token1 = await refreshAuthToken(installations); - expect(token1.token).to.equal(DB_AUTH_TOKEN); - - // Wait 30 minutes. - clock.tick('30:00'); - - const token2 = await refreshAuthToken(installations); - await expect(token2.token).to.equal(AUTH_TOKEN); - await expect(token2.token).not.to.equal(DB_AUTH_TOKEN); - expect(generateAuthTokenRequestSpy).to.be.calledOnce; - }); - }); - - describe('when there is an expired auth token in the DB', () => { - beforeEach(async () => { - const installationEntry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: DB_AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - 2 * ONE_WEEK_MS - } - }; - await set(installations.appConfig, installationEntry); - }); - - it('does not call generateAuthToken twice on subsequent calls', async () => { - await refreshAuthToken(installations); - await refreshAuthToken(installations); - expect(generateAuthTokenRequestSpy).to.be.calledOnce; - }); - - it('does not call generateAuthToken twice on simultaneous calls', async () => { - await Promise.all([ - refreshAuthToken(installations), - refreshAuthToken(installations) - ]); - expect(generateAuthTokenRequestSpy).to.be.calledOnce; - }); - - it('returns a new token', async () => { - const { token } = await refreshAuthToken(installations); - await expect(token).to.equal(AUTH_TOKEN); - await expect(token).not.to.equal(DB_AUTH_TOKEN); - expect(generateAuthTokenRequestSpy).to.be.calledOnce; - }); - - it('throws if the app is offline', async () => { - stub(navigator, 'onLine').value(false); - - await expect(refreshAuthToken(installations)).to.be.rejected; - }); - - it('saves the new token in the DB', async () => { - const { token } = await refreshAuthToken(installations); - - const installationEntry = (await get( - installations.appConfig - )) as RegisteredInstallationEntry; - expect(installationEntry).not.to.be.undefined; - expect(installationEntry.registrationStatus).to.equal( - RequestStatus.COMPLETED - ); - - const authToken = installationEntry.authToken as CompletedAuthToken; - expect(authToken.requestStatus).to.equal(RequestStatus.COMPLETED); - expect(authToken.token).to.equal(token); - }); - }); -}); diff --git a/packages-exp/installations-exp/src/helpers/refresh-auth-token.ts b/packages-exp/installations-exp/src/helpers/refresh-auth-token.ts deleted file mode 100644 index ac2a0ffc02d..00000000000 --- a/packages-exp/installations-exp/src/helpers/refresh-auth-token.ts +++ /dev/null @@ -1,214 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { generateAuthTokenRequest } from '../functions/generate-auth-token-request'; -import { - AppConfig, - FirebaseInstallationsImpl -} from '../interfaces/installation-impl'; -import { - AuthToken, - CompletedAuthToken, - InProgressAuthToken, - InstallationEntry, - RegisteredInstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { PENDING_TIMEOUT_MS, TOKEN_EXPIRATION_BUFFER } from '../util/constants'; -import { ERROR_FACTORY, ErrorCode, isServerError } from '../util/errors'; -import { sleep } from '../util/sleep'; -import { remove, set, update } from './idb-manager'; - -/** - * Returns a valid authentication token for the installation. Generates a new - * token if one doesn't exist, is expired or about to expire. - * - * Should only be called if the Firebase Installation is registered. - */ -export async function refreshAuthToken( - installations: FirebaseInstallationsImpl, - forceRefresh = false -): Promise { - let tokenPromise: Promise | undefined; - const entry = await update(installations.appConfig, oldEntry => { - if (!isEntryRegistered(oldEntry)) { - throw ERROR_FACTORY.create(ErrorCode.NOT_REGISTERED); - } - - const oldAuthToken = oldEntry.authToken; - if (!forceRefresh && isAuthTokenValid(oldAuthToken)) { - // There is a valid token in the DB. - return oldEntry; - } else if (oldAuthToken.requestStatus === RequestStatus.IN_PROGRESS) { - // There already is a token request in progress. - tokenPromise = waitUntilAuthTokenRequest(installations, forceRefresh); - return oldEntry; - } else { - // No token or token expired. - if (!navigator.onLine) { - throw ERROR_FACTORY.create(ErrorCode.APP_OFFLINE); - } - - const inProgressEntry = makeAuthTokenRequestInProgressEntry(oldEntry); - tokenPromise = fetchAuthTokenFromServer(installations, inProgressEntry); - return inProgressEntry; - } - }); - - const authToken = tokenPromise - ? await tokenPromise - : (entry.authToken as CompletedAuthToken); - return authToken; -} - -/** - * Call only if FID is registered and Auth Token request is in progress. - * - * Waits until the current pending request finishes. If the request times out, - * tries once in this thread as well. - */ -async function waitUntilAuthTokenRequest( - installations: FirebaseInstallationsImpl, - forceRefresh: boolean -): Promise { - // Unfortunately, there is no way of reliably observing when a value in - // IndexedDB changes (yet, see https://github.com/WICG/indexed-db-observers), - // so we need to poll. - - let entry = await updateAuthTokenRequest(installations.appConfig); - while (entry.authToken.requestStatus === RequestStatus.IN_PROGRESS) { - // generateAuthToken still in progress. - await sleep(100); - - entry = await updateAuthTokenRequest(installations.appConfig); - } - - const authToken = entry.authToken; - if (authToken.requestStatus === RequestStatus.NOT_STARTED) { - // The request timed out or failed in a different call. Try again. - return refreshAuthToken(installations, forceRefresh); - } else { - return authToken; - } -} - -/** - * Called only if there is a GenerateAuthToken request in progress. - * - * Updates the InstallationEntry in the DB based on the status of the - * GenerateAuthToken request. - * - * Returns the updated InstallationEntry. - */ -function updateAuthTokenRequest( - appConfig: AppConfig -): Promise { - return update(appConfig, oldEntry => { - if (!isEntryRegistered(oldEntry)) { - throw ERROR_FACTORY.create(ErrorCode.NOT_REGISTERED); - } - - const oldAuthToken = oldEntry.authToken; - if (hasAuthTokenRequestTimedOut(oldAuthToken)) { - return { - ...oldEntry, - authToken: { requestStatus: RequestStatus.NOT_STARTED } - }; - } - - return oldEntry; - }); -} - -async function fetchAuthTokenFromServer( - installations: FirebaseInstallationsImpl, - installationEntry: RegisteredInstallationEntry -): Promise { - try { - const authToken = await generateAuthTokenRequest( - installations, - installationEntry - ); - const updatedInstallationEntry: RegisteredInstallationEntry = { - ...installationEntry, - authToken - }; - await set(installations.appConfig, updatedInstallationEntry); - return authToken; - } catch (e) { - if ( - isServerError(e) && - (e.customData.serverCode === 401 || e.customData.serverCode === 404) - ) { - // Server returned a "FID not found" or a "Invalid authentication" error. - // Generate a new ID next time. - await remove(installations.appConfig); - } else { - const updatedInstallationEntry: RegisteredInstallationEntry = { - ...installationEntry, - authToken: { requestStatus: RequestStatus.NOT_STARTED } - }; - await set(installations.appConfig, updatedInstallationEntry); - } - throw e; - } -} - -function isEntryRegistered( - installationEntry: InstallationEntry | undefined -): installationEntry is RegisteredInstallationEntry { - return ( - installationEntry !== undefined && - installationEntry.registrationStatus === RequestStatus.COMPLETED - ); -} - -function isAuthTokenValid(authToken: AuthToken): boolean { - return ( - authToken.requestStatus === RequestStatus.COMPLETED && - !isAuthTokenExpired(authToken) - ); -} - -function isAuthTokenExpired(authToken: CompletedAuthToken): boolean { - const now = Date.now(); - return ( - now < authToken.creationTime || - authToken.creationTime + authToken.expiresIn < now + TOKEN_EXPIRATION_BUFFER - ); -} - -/** Returns an updated InstallationEntry with an InProgressAuthToken. */ -function makeAuthTokenRequestInProgressEntry( - oldEntry: RegisteredInstallationEntry -): RegisteredInstallationEntry { - const inProgressAuthToken: InProgressAuthToken = { - requestStatus: RequestStatus.IN_PROGRESS, - requestTime: Date.now() - }; - return { - ...oldEntry, - authToken: inProgressAuthToken - }; -} - -function hasAuthTokenRequestTimedOut(authToken: AuthToken): boolean { - return ( - authToken.requestStatus === RequestStatus.IN_PROGRESS && - authToken.requestTime + PENDING_TIMEOUT_MS < Date.now() - ); -} diff --git a/packages-exp/installations-exp/src/index.ts b/packages-exp/installations-exp/src/index.ts deleted file mode 100644 index 5aa6674dea3..00000000000 --- a/packages-exp/installations-exp/src/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Firebase Installations - * - * @packageDocumentation - */ - -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { registerInstallations } from './functions/config'; -import { registerVersion } from '@firebase/app-exp'; -import { name, version } from '../package.json'; - -export * from './api'; -export * from './interfaces/public-types'; - -registerInstallations(); -registerVersion(name, version); diff --git a/packages-exp/installations-exp/src/interfaces/api-response.ts b/packages-exp/installations-exp/src/interfaces/api-response.ts deleted file mode 100644 index f7560a6925c..00000000000 --- a/packages-exp/installations-exp/src/interfaces/api-response.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export interface CreateInstallationResponse { - readonly refreshToken: string; - readonly authToken: GenerateAuthTokenResponse; - readonly fid?: string; -} - -export interface GenerateAuthTokenResponse { - readonly token: string; - - /** - * Encoded as a string with the suffix 's' (indicating seconds), preceded by - * the number of seconds. - * - * Example: "604800s". - */ - readonly expiresIn: string; -} diff --git a/packages-exp/installations-exp/src/interfaces/installation-entry.ts b/packages-exp/installations-exp/src/interfaces/installation-entry.ts deleted file mode 100644 index 4b30aa2486f..00000000000 --- a/packages-exp/installations-exp/src/interfaces/installation-entry.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** Status of a server request. */ -export const enum RequestStatus { - NOT_STARTED, - IN_PROGRESS, - COMPLETED -} - -export interface NotStartedAuthToken { - readonly requestStatus: RequestStatus.NOT_STARTED; -} - -export interface InProgressAuthToken { - readonly requestStatus: RequestStatus.IN_PROGRESS; - - /** - * Unix timestamp when the current generateAuthRequest was initiated. - * Used for figuring out how long the request status has been IN_PROGRESS. - */ - readonly requestTime: number; -} - -export interface CompletedAuthToken { - readonly requestStatus: RequestStatus.COMPLETED; - - /** - * Firebase Installations Authentication Token. - * Only exists if requestStatus is COMPLETED. - */ - readonly token: string; - - /** - * Unix timestamp when Authentication Token was created. - * Only exists if requestStatus is COMPLETED. - */ - readonly creationTime: number; - - /** - * Authentication Token time to live duration in milliseconds. - * Only exists if requestStatus is COMPLETED. - */ - readonly expiresIn: number; -} - -export type AuthToken = - | NotStartedAuthToken - | InProgressAuthToken - | CompletedAuthToken; - -export interface UnregisteredInstallationEntry { - /** Status of the Firebase Installation registration on the server. */ - readonly registrationStatus: RequestStatus.NOT_STARTED; - - /** Firebase Installation ID */ - readonly fid: string; -} - -export interface InProgressInstallationEntry { - /** Status of the Firebase Installation registration on the server. */ - readonly registrationStatus: RequestStatus.IN_PROGRESS; - - /** - * Unix timestamp that shows the time when the current createInstallation - * request was initiated. - * Used for figuring out how long the registration status has been PENDING. - */ - readonly registrationTime: number; - - /** Firebase Installation ID */ - readonly fid: string; -} - -export interface RegisteredInstallationEntry { - /** Status of the Firebase Installation registration on the server. */ - readonly registrationStatus: RequestStatus.COMPLETED; - - /** Firebase Installation ID */ - readonly fid: string; - - /** - * Refresh Token returned from the server. - * Used for authenticating generateAuthToken requests. - */ - readonly refreshToken: string; - - /** Firebase Installation Authentication Token. */ - readonly authToken: AuthToken; -} - -/** Firebase Installation ID and related data in the database. */ -export type InstallationEntry = - | UnregisteredInstallationEntry - | InProgressInstallationEntry - | RegisteredInstallationEntry; diff --git a/packages-exp/installations-exp/src/testing/compare-headers.test.ts b/packages-exp/installations-exp/src/testing/compare-headers.test.ts deleted file mode 100644 index 8bd6fb81203..00000000000 --- a/packages-exp/installations-exp/src/testing/compare-headers.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AssertionError, expect } from 'chai'; -import '../testing/setup'; -import { compareHeaders } from './compare-headers'; - -describe('compareHeaders', () => { - it("doesn't fail if headers contain the same entries", () => { - const headers1 = new Headers({ a: '123', b: '456' }); - const headers2 = new Headers({ a: '123', b: '456' }); - compareHeaders(headers1, headers2); - }); - - it('fails if headers contain different keys', () => { - const headers1 = new Headers({ a: '123', b: '456', extraKey: '789' }); - const headers2 = new Headers({ a: '123', b: '456' }); - expect(() => { - compareHeaders(headers1, headers2); - }).to.throw(AssertionError); - }); - - it('fails if headers contain different values', () => { - const headers1 = new Headers({ a: '123', b: '456' }); - const headers2 = new Headers({ a: '123', b: 'differentValue' }); - expect(() => { - compareHeaders(headers1, headers2); - }).to.throw(AssertionError); - }); -}); diff --git a/packages-exp/installations-exp/src/testing/compare-headers.ts b/packages-exp/installations-exp/src/testing/compare-headers.ts deleted file mode 100644 index 5b93c14933e..00000000000 --- a/packages-exp/installations-exp/src/testing/compare-headers.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AssertionError, expect } from 'chai'; - -// Trick TS since it's set to target ES5. -declare class HeadersWithEntries extends Headers { - entries?(): Iterable<[string, string]>; -} - -// Chai doesn't check if Headers objects contain the same entries, -// so we need to do that manually. -export function compareHeaders( - expectedHeaders: HeadersWithEntries, - actualHeaders: HeadersWithEntries -): void { - if ( - expectedHeaders.entries === undefined || - actualHeaders.entries === undefined - ) { - throw new AssertionError('Headers object does not have entries method'); - } - - const expected = new Map(Array.from(expectedHeaders.entries())); - const actual = new Map(Array.from(actualHeaders.entries())); - expect(actual).to.deep.equal(expected); -} diff --git a/packages-exp/installations-exp/src/testing/fake-generators.ts b/packages-exp/installations-exp/src/testing/fake-generators.ts deleted file mode 100644 index 0240b263943..00000000000 --- a/packages-exp/installations-exp/src/testing/fake-generators.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp } from '@firebase/app-exp'; -import { - Component, - ComponentContainer, - ComponentType -} from '@firebase/component'; -import { extractAppConfig } from '../helpers/extract-app-config'; -import { - FirebaseInstallationsImpl, - AppConfig -} from '../interfaces/installation-impl'; - -export function getFakeApp(): FirebaseApp { - return { - name: 'appName', - options: { - apiKey: 'apiKey', - projectId: 'projectId', - authDomain: 'authDomain', - messagingSenderId: 'messagingSenderId', - databaseURL: 'databaseUrl', - storageBucket: 'storageBucket', - appId: '1:777777777777:web:d93b5ca1475efe57' - }, - automaticDataCollectionEnabled: true - }; -} - -export function getFakeAppConfig( - customValues: Partial = {} -): AppConfig { - return { ...extractAppConfig(getFakeApp()), ...customValues }; -} - -export function getFakeInstallations(): FirebaseInstallationsImpl { - const container = new ComponentContainer('test'); - container.addComponent( - new Component( - 'platform-logger', - () => ({ getPlatformInfoString: () => 'a/1.2.3 b/2.3.4' }), - ComponentType.PRIVATE - ) - ); - - return { - app: getFakeApp(), - appConfig: getFakeAppConfig(), - platformLoggerProvider: container.getProvider('platform-logger'), - _delete: () => { - return Promise.resolve(); - } - }; -} diff --git a/packages-exp/installations-exp/src/testing/setup.ts b/packages-exp/installations-exp/src/testing/setup.ts deleted file mode 100644 index 3db746533e0..00000000000 --- a/packages-exp/installations-exp/src/testing/setup.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { use } from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; -import { restore } from 'sinon'; -import * as sinonChai from 'sinon-chai'; -import { clear } from '../helpers/idb-manager'; - -use(chaiAsPromised); -use(sinonChai); - -afterEach(async () => { - restore(); - await clear(); -}); diff --git a/packages-exp/installations-exp/src/util/constants.ts b/packages-exp/installations-exp/src/util/constants.ts deleted file mode 100644 index c20fa260274..00000000000 --- a/packages-exp/installations-exp/src/util/constants.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { version } from '../../package.json'; - -export const PENDING_TIMEOUT_MS = 10000; - -export const PACKAGE_VERSION = `w:${version}`; -export const INTERNAL_AUTH_VERSION = 'FIS_v2'; - -export const INSTALLATIONS_API_URL = - 'https://firebaseinstallations.googleapis.com/v1'; - -export const TOKEN_EXPIRATION_BUFFER = 60 * 60 * 1000; // One hour - -export const SERVICE = 'installations'; -export const SERVICE_NAME = 'Installations'; diff --git a/packages-exp/installations-exp/src/util/errors.ts b/packages-exp/installations-exp/src/util/errors.ts deleted file mode 100644 index 25cc69b1ff0..00000000000 --- a/packages-exp/installations-exp/src/util/errors.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorFactory, FirebaseError } from '@firebase/util'; -import { SERVICE, SERVICE_NAME } from './constants'; - -export const enum ErrorCode { - MISSING_APP_CONFIG_VALUES = 'missing-app-config-values', - NOT_REGISTERED = 'not-registered', - INSTALLATION_NOT_FOUND = 'installation-not-found', - REQUEST_FAILED = 'request-failed', - APP_OFFLINE = 'app-offline', - DELETE_PENDING_REGISTRATION = 'delete-pending-registration' -} - -const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = { - [ErrorCode.MISSING_APP_CONFIG_VALUES]: - 'Missing App configuration value: "{$valueName}"', - [ErrorCode.NOT_REGISTERED]: 'Firebase Installation is not registered.', - [ErrorCode.INSTALLATION_NOT_FOUND]: 'Firebase Installation not found.', - [ErrorCode.REQUEST_FAILED]: - '{$requestName} request failed with error "{$serverCode} {$serverStatus}: {$serverMessage}"', - [ErrorCode.APP_OFFLINE]: 'Could not process request. Application offline.', - [ErrorCode.DELETE_PENDING_REGISTRATION]: - "Can't delete installation while there is a pending registration request." -}; - -interface ErrorParams { - [ErrorCode.MISSING_APP_CONFIG_VALUES]: { - valueName: string; - }; - [ErrorCode.REQUEST_FAILED]: { - requestName: string; - [index: string]: string | number; // to make Typescript 3.8 happy - } & ServerErrorData; -} - -export const ERROR_FACTORY = new ErrorFactory( - SERVICE, - SERVICE_NAME, - ERROR_DESCRIPTION_MAP -); - -export interface ServerErrorData { - serverCode: number; - serverMessage: string; - serverStatus: string; -} - -export type ServerError = FirebaseError & { customData: ServerErrorData }; - -/** Returns true if error is a FirebaseError that is based on an error from the server. */ -export function isServerError(error: unknown): error is ServerError { - return ( - error instanceof FirebaseError && - error.code.includes(ErrorCode.REQUEST_FAILED) - ); -} diff --git a/packages-exp/installations-exp/src/util/get-key.ts b/packages-exp/installations-exp/src/util/get-key.ts deleted file mode 100644 index 272d342d366..00000000000 --- a/packages-exp/installations-exp/src/util/get-key.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AppConfig } from '../interfaces/installation-impl'; - -/** Returns a string key that can be used to identify the app. */ -export function getKey(appConfig: AppConfig): string { - return `${appConfig.appName}!${appConfig.appId}`; -} diff --git a/packages-exp/installations-exp/src/util/sleep.test.ts b/packages-exp/installations-exp/src/util/sleep.test.ts deleted file mode 100644 index 6dfc4b328ee..00000000000 --- a/packages-exp/installations-exp/src/util/sleep.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonFakeTimers, useFakeTimers } from 'sinon'; -import '../testing/setup'; -import { sleep } from './sleep'; - -describe('sleep', () => { - let clock: SinonFakeTimers; - - beforeEach(() => { - clock = useFakeTimers({ shouldAdvanceTime: true }); - }); - - it('returns a promise that resolves after a given amount of time', async () => { - const t0 = clock.now; - await sleep(100); - const t1 = clock.now; - - expect(t1 - t0).to.equal(100); - }); -}); diff --git a/packages-exp/installations-exp/src/util/sleep.ts b/packages-exp/installations-exp/src/util/sleep.ts deleted file mode 100644 index 2bd1eb9283b..00000000000 --- a/packages-exp/installations-exp/src/util/sleep.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** Returns a promise that resolves after given time passes. */ -export function sleep(ms: number): Promise { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -} diff --git a/packages-exp/installations-exp/test-app/.gitignore b/packages-exp/installations-exp/test-app/.gitignore deleted file mode 100644 index e706d63f780..00000000000 --- a/packages-exp/installations-exp/test-app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -sdk.js -sdk.js.map diff --git a/packages-exp/installations-exp/test-app/index.html b/packages-exp/installations-exp/test-app/index.html deleted file mode 100644 index f5e2958cea0..00000000000 --- a/packages-exp/installations-exp/test-app/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - Test App - - - - -

- - -

-

- - -

-

- - -

-

- - -

-

- - - - -

-

Requests

-
-

Database Contents

-
- - - diff --git a/packages-exp/installations-exp/test-app/index.js b/packages-exp/installations-exp/test-app/index.js deleted file mode 100644 index d0101e3114e..00000000000 --- a/packages-exp/installations-exp/test-app/index.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const DATABASE_NAME = 'firebase-installations-database'; -const DATABASE_VERSION = 1; -const OBJECT_STORE_NAME = 'firebase-installations-store'; - -const requestLogs = []; -let db; - -window.indexedDB.open(DATABASE_NAME, DATABASE_VERSION).onsuccess = event => { - db = event.target.result; - setInterval(refreshDatabase, 1000); -}; - -function refreshDatabase() { - const request = db - .transaction(OBJECT_STORE_NAME, 'readwrite') - .objectStore(OBJECT_STORE_NAME) - .getAll(); - - request.onsuccess = () => { - const dbElement = getElement('database'); - dbElement.innerHTML = request.result - .map(v => `

${format(v)}

`) - .join(''); - }; -} - -function clearDb() { - const request = db - .transaction(OBJECT_STORE_NAME, 'readwrite') - .objectStore(OBJECT_STORE_NAME) - .clear(); - request.onsuccess = refreshDatabase; -} - -function getElement(id) { - const element = document.getElementById(id); - if (!element) { - throw new Error(`Element not found: ${id}`); - } - return element; -} - -function getInputValue(elementId) { - const element = getElement(elementId); - return element.value; -} - -function getId() { - printRequest('Get ID', FirebaseInstallations.getId(getApp())); -} - -function getToken() { - printRequest('Get Token', FirebaseInstallations.getToken(getApp())); -} - -function deleteInstallation() { - printRequest( - 'Delete Installation', - FirebaseInstallations.deleteInstallation(getApp()) - ); -} - -async function printRequest(requestInfo, promise) { - const requestsElement = getElement('requests'); - requestsElement.innerHTML = '

Loading...

' + requestLogs.join(''); - let result; - try { - const request = await promise; - result = request ? format(request) : 'Completed successfully'; - } catch (e) { - result = e.toString(); - } - requestLogs.unshift(`

${requestInfo}:
${result}

`); - requestsElement.innerHTML = requestLogs.join(''); -} - -function format(o) { - const escapedString = JSON.stringify(o, null, 2); - return `${escapedString}`; -} - -function getApp() { - const appName = getInputValue('appName'); - const projectId = getInputValue('projectId'); - const apiKey = getInputValue('apiKey'); - const appId = getInputValue('appId'); - return { - name: appName, - appConfig: { appName, projectId, apiKey, appId } - }; -} - -getElement('getId').onclick = getId; -getElement('getToken').onclick = getToken; -getElement('deleteInstallation').onclick = deleteInstallation; -getElement('clearDb').onclick = clearDb; diff --git a/packages-exp/installations-exp/test-app/rollup.config.js b/packages-exp/installations-exp/test-app/rollup.config.js deleted file mode 100644 index 6f6d3a3eae7..00000000000 --- a/packages-exp/installations-exp/test-app/rollup.config.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import json from '@rollup/plugin-json'; -import { uglify } from 'rollup-plugin-uglify'; -import typescript from 'typescript'; - -/** - * Creates an iife build to run with the Test App. - */ -export default [ - { - input: 'src/api/index.ts', - output: { - name: 'FirebaseInstallations', - file: 'test-app/sdk.js', - format: 'iife', - sourcemap: true - }, - plugins: [ - typescriptPlugin({ - typescript, - tsconfigOverride: { compilerOptions: { declaration: false } } - }), - json(), - resolve(), - commonjs(), - uglify() - ] - } -]; diff --git a/packages-exp/installations-exp/tsconfig.json b/packages-exp/installations-exp/tsconfig.json deleted file mode 100644 index 420eda97a1d..00000000000 --- a/packages-exp/installations-exp/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "downlevelIteration": true, - "resolveJsonModule": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "exclude": ["dist/**/*"] -} diff --git a/packages-exp/messaging-exp/.eslintrc.js b/packages-exp/messaging-exp/.eslintrc.js deleted file mode 100644 index ca80aa0f69a..00000000000 --- a/packages-exp/messaging-exp/.eslintrc.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = { - extends: '../../config/.eslintrc.js', - parserOptions: { - project: 'tsconfig.json', - // to make vscode-eslint work with monorepo - // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 - tsconfigRootDir: __dirname - } -}; diff --git a/packages-exp/messaging-exp/README.md b/packages-exp/messaging-exp/README.md deleted file mode 100644 index 8f3fd52738a..00000000000 --- a/packages-exp/messaging-exp/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @firebase/messaging - -This is the Firebase Cloud Messaging component of the Firebase JS SDK. - -**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/messaging-exp/karma.conf.js b/packages-exp/messaging-exp/karma.conf.js deleted file mode 100644 index c9bc6b770c9..00000000000 --- a/packages-exp/messaging-exp/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const karmaBase = require('../../config/karma.base'); - -const files = [`src/**/*.test.ts`]; - -module.exports = function (config) { - const karmaConfig = { - ...karmaBase, - files, - frameworks: ['mocha'] - }; - - config.set(karmaConfig); -}; - -module.exports.files = files; diff --git a/packages-exp/messaging-exp/package.json b/packages-exp/messaging-exp/package.json deleted file mode 100644 index acb9e9feaa2..00000000000 --- a/packages-exp/messaging-exp/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "@firebase/messaging-exp", - "private": true, - "version": "0.0.900", - "description": "", - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.cjs.js", - "browser": "dist/index.esm2017.js", - "module": "dist/index.esm2017.js", - "typings": "dist/index.d.ts", - "sw": "dist/index.sw.esm2017.js", - "files": [ - "dist", - "sw/package.json" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:deps": "lerna run --scope @firebase/'{app-exp,messaging-exp}' --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", - "dev": "rollup -c -w", - "test": "run-p test:karma type-check lint ", - "test:integration": "run-p test:karma type-check lint && cd ../../integration/messaging && npm run-script test", - "test:ci": "node ../../scripts/run_tests_in_ci.js", - "test:karma": "karma start --single-run", - "test:debug": "karma start --browsers=Chrome --auto-watch", - "api-report": "yarn api-report:rollup && yarn api-report:api-json", - "api-report:rollup": "ts-node-script ../../repo-scripts/prune-dts/extract-public-api.ts --package messaging-exp --packageRoot . --typescriptDts ./dist/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/index-public.d.ts", - "api-report:api-json": "api-extractor run --local --verbose", - "type-check": "tsc --noEmit", - "typings:public": "node ../../scripts/exp/use_typings.js ./dist/index-public.d.ts" - }, - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-exp": "0.x" - }, - "dependencies": { - "@firebase/component": "0.5.6", - "@firebase/installations-exp": "0.0.900", - "@firebase/messaging-interop-types": "0.0.1", - "@firebase/util": "1.3.0", - "idb": "3.0.2", - "tslib": "^2.1.0" - }, - "devDependencies": { - "@rollup/plugin-json": "4.1.0", - "rollup-plugin-typescript2": "0.30.0", - "ts-essentials": "7.0.1", - "typescript": "4.2.2" - }, - "repository": { - "directory": "packages/messaging", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "esm5": "dist/index.esm.js" -} diff --git a/packages-exp/messaging-exp/rollup.config.release.js b/packages-exp/messaging-exp/rollup.config.release.js deleted file mode 100644 index 03ccf1d097f..00000000000 --- a/packages-exp/messaging-exp/rollup.config.release.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import json from '@rollup/plugin-json'; -import pkg from './package.json'; -import typescript from 'typescript'; -import typescriptPlugin from 'rollup-plugin-typescript2'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = [ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: (id, external) => id === '@firebase/installations' - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - abortOnError: false, - clean: true, - transformers: [importPathTransformer], - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = [ - { - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: (id, external) => id === '@firebase/installations' - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - - // sw builds - { - input: 'src/index.sw.ts', - output: { file: pkg.sw, format: 'es', sourcemap: true }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: (id, external) => id === '@firebase/installations' - } - } -]; - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/messaging-exp/src/helpers/array-base64-translator.test.ts b/packages-exp/messaging-exp/src/helpers/array-base64-translator.test.ts deleted file mode 100644 index c161b365dbc..00000000000 --- a/packages-exp/messaging-exp/src/helpers/array-base64-translator.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import { arrayToBase64, base64ToArray } from './array-base64-translator'; - -import { expect } from 'chai'; - -// prettier-ignore -const TEST_P256_ARRAY = new Uint8Array([ - 4, 181, 98, 240, 48, 62, 75, 119, 193, 227, 154, 69, 250, 216, 53, 110, - 157, 120, 62, 76, 213, 249, 11, 62, 12, 19, 149, 36, 5, 82, 140, 37, 141, - 134, 132, 98, 87, 152, 175, 98, 53, 83, 196, 242, 202, 155, 19, 173, 157, - 216, 45, 147, 20, 12, 151, 160, 147, 159, 205, 219, 75, 133, 156, 129, 152 -]); -const TEST_P256_BASE64 = - 'BLVi8DA-S3fB45pF-tg1bp14PkzV-Qs-DBOVJAVSjCWNhoRi' + - 'V5ivYjVTxPLKmxOtndgtkxQMl6CTn83bS4WcgZg'; - -// prettier-ignore -const TEST_AUTH_ARRAY = new Uint8Array([ - 255, 237, 107, 177, 171, 78, 84, 131, 221, 231, 87, 188, 22, 232, 71, 15 -]); -const TEST_AUTH_BASE64 = '_-1rsatOVIPd51e8FuhHDw'; - -// prettier-ignore -const TEST_VAPID_ARRAY = new Uint8Array([4, 48, 191, 217, 11, 218, 74, 124, 103, 143, 63, 182, 203, - 91, 0, 68, 221, 68, 172, 74, 89, 133, 198, 252, 145, 164, 136, 243, 186, 75, 198, 32, 45, 64, 240, - 120, 141, 173, 240, 131, 253, 83, 209, 193, 129, 50, 155, 126, 189, 23, 127, 232, 109, 75, 101, - 229, 92, 85, 137, 80, 121, 35, 229, 118, 207]); -const TEST_VAPID_BASE64 = - 'BDC_2QvaSnxnjz-2y1sARN1ErEpZhcb8kaSI87pLxiAtQPB4ja3wg_1T0cGBMpt' + - '-vRd_6G1LZeVcVYlQeSPlds8'; - -describe('arrayToBase64', () => { - it('array to base64 translation succeed', () => { - expect(arrayToBase64(TEST_P256_ARRAY)).to.equal(TEST_P256_BASE64); - expect(arrayToBase64(TEST_AUTH_ARRAY)).to.equal(TEST_AUTH_BASE64); - expect(arrayToBase64(TEST_VAPID_ARRAY)).to.equal(TEST_VAPID_BASE64); - }); -}); - -describe('base64ToArray', () => { - it('base64 to array translation succeed', () => { - expect(isEqual(base64ToArray(TEST_P256_BASE64), TEST_P256_ARRAY)).to.equal( - true - ); - expect(isEqual(base64ToArray(TEST_AUTH_BASE64), TEST_AUTH_ARRAY)).to.equal( - true - ); - expect( - isEqual(base64ToArray(TEST_VAPID_BASE64), TEST_VAPID_ARRAY) - ).to.equal(true); - }); -}); - -function isEqual(a: Uint8Array, b: Uint8Array): boolean { - if (a.length !== b.length) { - return false; - } - - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - return false; - } - } - - return true; -} diff --git a/packages-exp/messaging-exp/src/helpers/array-base64-translator.ts b/packages-exp/messaging-exp/src/helpers/array-base64-translator.ts deleted file mode 100644 index bbade845ae4..00000000000 --- a/packages-exp/messaging-exp/src/helpers/array-base64-translator.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export function arrayToBase64(array: Uint8Array | ArrayBuffer): string { - const uint8Array = new Uint8Array(array); - const base64String = btoa(String.fromCharCode(...uint8Array)); - return base64String.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); -} - -export function base64ToArray(base64String: string): Uint8Array { - const padding = '='.repeat((4 - (base64String.length % 4)) % 4); - const base64 = (base64String + padding) - .replace(/\-/g, '+') - .replace(/_/g, '/'); - - const rawData = atob(base64); - const outputArray = new Uint8Array(rawData.length); - - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; -} diff --git a/packages-exp/messaging-exp/src/helpers/externalizePayload.test.ts b/packages-exp/messaging-exp/src/helpers/externalizePayload.test.ts deleted file mode 100644 index d68520c055a..00000000000 --- a/packages-exp/messaging-exp/src/helpers/externalizePayload.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { MessagePayload } from '../interfaces/public-types'; -import { MessagePayloadInternal } from '../interfaces/internal-message-payload'; -import { expect } from 'chai'; -import { externalizePayload } from './externalizePayload'; - -describe('externalizePayload', () => { - it('externalizes internalMessage with only notification payload', () => { - const internalPayload: MessagePayloadInternal = { - notification: { - title: 'title', - body: 'body', - image: 'image' - }, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - const payload: MessagePayload = { - notification: { title: 'title', body: 'body', image: 'image' }, - from: 'from', - collapseKey: 'collapse', - messageId: 'mid' - }; - expect(externalizePayload(internalPayload)).to.deep.equal(payload); - }); - - it('externalizes internalMessage with only data payload', () => { - const internalPayload: MessagePayloadInternal = { - data: { - foo: 'foo', - bar: 'bar', - baz: 'baz' - }, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - const payload: MessagePayload = { - data: { foo: 'foo', bar: 'bar', baz: 'baz' }, - from: 'from', - collapseKey: 'collapse', - messageId: 'mid' - }; - expect(externalizePayload(internalPayload)).to.deep.equal(payload); - }); - - it('externalizes internalMessage with all three payloads', () => { - const internalPayload: MessagePayloadInternal = { - notification: { - title: 'title', - body: 'body', - image: 'image' - }, - data: { - foo: 'foo', - bar: 'bar', - baz: 'baz' - }, - fcmOptions: { - link: 'link', - // eslint-disable-next-line camelcase - analytics_label: 'label' - }, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - const payload: MessagePayload = { - notification: { - title: 'title', - body: 'body', - image: 'image' - }, - data: { - foo: 'foo', - bar: 'bar', - baz: 'baz' - }, - fcmOptions: { - link: 'link', - analyticsLabel: 'label' - }, - from: 'from', - collapseKey: 'collapse', - messageId: 'mid' - }; - expect(externalizePayload(internalPayload)).to.deep.equal(payload); - }); -}); diff --git a/packages-exp/messaging-exp/src/helpers/externalizePayload.ts b/packages-exp/messaging-exp/src/helpers/externalizePayload.ts deleted file mode 100644 index 795e8ac4a63..00000000000 --- a/packages-exp/messaging-exp/src/helpers/externalizePayload.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { MessagePayload } from '../interfaces/public-types'; -import { MessagePayloadInternal } from '../interfaces/internal-message-payload'; - -export function externalizePayload( - internalPayload: MessagePayloadInternal -): MessagePayload { - const payload: MessagePayload = { - from: internalPayload.from, - // eslint-disable-next-line camelcase - collapseKey: internalPayload.collapse_key, - // eslint-disable-next-line camelcase - messageId: internalPayload.fcm_message_id - } as MessagePayload; - - propagateNotificationPayload(payload, internalPayload); - propagateDataPayload(payload, internalPayload); - propagateFcmOptions(payload, internalPayload); - - return payload; -} - -function propagateNotificationPayload( - payload: MessagePayload, - messagePayloadInternal: MessagePayloadInternal -): void { - if (!messagePayloadInternal.notification) { - return; - } - - payload.notification = {}; - - const title = messagePayloadInternal.notification!.title; - if (!!title) { - payload.notification!.title = title; - } - - const body = messagePayloadInternal.notification!.body; - if (!!body) { - payload.notification!.body = body; - } - - const image = messagePayloadInternal.notification!.image; - if (!!image) { - payload.notification!.image = image; - } -} - -function propagateDataPayload( - payload: MessagePayload, - messagePayloadInternal: MessagePayloadInternal -): void { - if (!messagePayloadInternal.data) { - return; - } - - payload.data = messagePayloadInternal.data as { [key: string]: string }; -} - -function propagateFcmOptions( - payload: MessagePayload, - messagePayloadInternal: MessagePayloadInternal -): void { - if (!messagePayloadInternal.fcmOptions) { - return; - } - - payload.fcmOptions = {}; - - const link = messagePayloadInternal.fcmOptions!.link; - if (!!link) { - payload.fcmOptions!.link = link; - } - - // eslint-disable-next-line camelcase - const analyticsLabel = messagePayloadInternal.fcmOptions!.analytics_label; - if (!!analyticsLabel) { - payload.fcmOptions!.analyticsLabel = analyticsLabel; - } -} diff --git a/packages-exp/messaging-exp/src/helpers/extract-app-config.test.ts b/packages-exp/messaging-exp/src/helpers/extract-app-config.test.ts deleted file mode 100644 index f3f91d604ab..00000000000 --- a/packages-exp/messaging-exp/src/helpers/extract-app-config.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseApp } from '@firebase/app-exp'; -import { expect } from 'chai'; -import { extractAppConfig } from './extract-app-config'; -import { getFakeApp } from '../testing/fakes/firebase-dependencies'; - -describe('extractAppConfig', () => { - it('returns AppConfig if the argument is a FirebaseApp object that includes an appId', () => { - const firebaseApp = getFakeApp(); - const expected: AppConfig = { - appName: 'appName', - apiKey: 'apiKey', - projectId: 'projectId', - appId: '1:777777777777:web:d93b5ca1475efe57', - senderId: '1234567890' - }; - expect(extractAppConfig(firebaseApp)).to.deep.equal(expected); - }); - - it('throws if a necessary value is missing', () => { - expect(() => - extractAppConfig((undefined as unknown) as FirebaseApp) - ).to.throw('Missing App configuration value: "App Configuration Object"'); - - let firebaseApp = getFakeApp(); - delete firebaseApp.options; - expect(() => extractAppConfig(firebaseApp)).to.throw( - 'Missing App configuration value: "App Configuration Object"' - ); - - firebaseApp = getFakeApp(); - delete firebaseApp.name; - expect(() => extractAppConfig(firebaseApp)).to.throw( - 'Missing App configuration value: "App Name"' - ); - - firebaseApp = getFakeApp(); - delete firebaseApp.options.projectId; - expect(() => extractAppConfig(firebaseApp)).to.throw( - 'Missing App configuration value: "projectId"' - ); - - firebaseApp = getFakeApp(); - delete firebaseApp.options.apiKey; - expect(() => extractAppConfig(firebaseApp)).to.throw( - 'Missing App configuration value: "apiKey"' - ); - - firebaseApp = getFakeApp(); - delete firebaseApp.options.appId; - expect(() => extractAppConfig(firebaseApp)).to.throw( - 'Missing App configuration value: "appId"' - ); - - firebaseApp = getFakeApp(); - delete firebaseApp.options.messagingSenderId; - expect(() => extractAppConfig(firebaseApp)).to.throw( - 'Missing App configuration value: "messagingSenderId"' - ); - }); -}); diff --git a/packages-exp/messaging-exp/src/helpers/extract-app-config.ts b/packages-exp/messaging-exp/src/helpers/extract-app-config.ts deleted file mode 100644 index c80a32ee14d..00000000000 --- a/packages-exp/messaging-exp/src/helpers/extract-app-config.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { FirebaseApp, FirebaseOptions } from '@firebase/app-exp'; - -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseError } from '@firebase/util'; - -export function extractAppConfig(app: FirebaseApp): AppConfig { - if (!app || !app.options) { - throw getMissingValueError('App Configuration Object'); - } - - if (!app.name) { - throw getMissingValueError('App Name'); - } - - // Required app config keys - const configKeys: ReadonlyArray = [ - 'projectId', - 'apiKey', - 'appId', - 'messagingSenderId' - ]; - - const { options } = app; - for (const keyName of configKeys) { - if (!options[keyName]) { - throw getMissingValueError(keyName); - } - } - - return { - appName: app.name, - projectId: options.projectId!, - apiKey: options.apiKey!, - appId: options.appId!, - senderId: options.messagingSenderId! - }; -} - -function getMissingValueError(valueName: string): FirebaseError { - return ERROR_FACTORY.create(ErrorCode.MISSING_APP_CONFIG_VALUES, { - valueName - }); -} diff --git a/packages-exp/messaging-exp/src/helpers/is-console-message.ts b/packages-exp/messaging-exp/src/helpers/is-console-message.ts deleted file mode 100644 index 151713be132..00000000000 --- a/packages-exp/messaging-exp/src/helpers/is-console-message.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CONSOLE_CAMPAIGN_ID } from '../util/constants'; -import { ConsoleMessageData } from '../interfaces/internal-message-payload'; - -export function isConsoleMessage(data: unknown): data is ConsoleMessageData { - // This message has a campaign ID, meaning it was sent using the Firebase Console. - return typeof data === 'object' && !!data && CONSOLE_CAMPAIGN_ID in data; -} diff --git a/packages-exp/messaging-exp/src/helpers/migrate-old-database.test.ts b/packages-exp/messaging-exp/src/helpers/migrate-old-database.test.ts deleted file mode 100644 index 020295ca2fd..00000000000 --- a/packages-exp/messaging-exp/src/helpers/migrate-old-database.test.ts +++ /dev/null @@ -1,204 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import { - V2TokenDetails, - V3TokenDetails, - V4TokenDetails, - migrateOldDatabase -} from './migrate-old-database'; - -import { FakePushSubscription } from '../testing/fakes/service-worker'; -import { base64ToArray } from './array-base64-translator'; -import { expect } from 'chai'; -import { getFakeTokenDetails } from '../testing/fakes/token-details'; -import { openDb } from 'idb'; - -describe('migrateOldDb', () => { - it("does nothing if old DB didn't exist", async () => { - const tokenDetails = await migrateOldDatabase('1234567890'); - expect(tokenDetails).to.be.null; - }); - - it('does nothing if old DB was too old', async () => { - await put(1, { - swScope: '/scope-value', - fcmSenderId: '1234567890', - fcmToken: 'token-value' - }); - - const tokenDetails = await migrateOldDatabase('1234567890'); - expect(tokenDetails).to.be.null; - }); - - describe('version 2', () => { - beforeEach(async () => { - const v2TokenDetails: V2TokenDetails = { - fcmToken: 'token-value', - swScope: '/scope-value', - vapidKey: base64ToArray('dmFwaWQta2V5LXZhbHVl'), - fcmSenderId: '1234567890', - fcmPushSet: '7654321', - auth: 'YXV0aC12YWx1ZQ', - p256dh: 'cDI1Ni12YWx1ZQ', - endpoint: 'https://example.org', - subscription: new FakePushSubscription() - }; - - await put(2, v2TokenDetails); - }); - - it('can get a value from old DB', async () => { - const tokenDetails = await migrateOldDatabase('1234567890'); - - const expectedTokenDetails = getFakeTokenDetails(); - // Ignore createTime difference. - expectedTokenDetails.createTime = tokenDetails!.createTime; - - expect(tokenDetails).to.deep.equal(expectedTokenDetails); - }); - - it('only migrates once', async () => { - await migrateOldDatabase('1234567890'); - const tokenDetails = await migrateOldDatabase('1234567890'); - - expect(tokenDetails).to.be.null; - }); - - it('does not get a value that has a different sender ID', async () => { - const tokenDetails = await migrateOldDatabase('321321321'); - expect(tokenDetails).to.be.null; - }); - - it('does not migrate an entry with missing optional values', async () => { - const v2TokenDetails: V2TokenDetails = { - fcmToken: 'token-value', - swScope: '/scope-value', - vapidKey: base64ToArray('dmFwaWQta2V5LXZhbHVl'), - fcmSenderId: '1234567890', - fcmPushSet: '7654321', - subscription: new FakePushSubscription() - }; - await put(2, v2TokenDetails); - - const tokenDetails = await migrateOldDatabase('1234567890'); - expect(tokenDetails).to.be.null; - }); - }); - - describe('version 3', () => { - beforeEach(async () => { - const v3TokenDetails: V3TokenDetails = { - createTime: 1234567890, - fcmToken: 'token-value', - swScope: '/scope-value', - vapidKey: base64ToArray('dmFwaWQta2V5LXZhbHVl'), - fcmSenderId: '1234567890', - fcmPushSet: '7654321', - auth: base64ToArray('YXV0aC12YWx1ZQ'), - p256dh: base64ToArray('cDI1Ni12YWx1ZQ'), - endpoint: 'https://example.org' - }; - - await put(3, v3TokenDetails); - }); - - it('can get a value from old DB', async () => { - const tokenDetails = await migrateOldDatabase('1234567890'); - - const expectedTokenDetails = getFakeTokenDetails(); - - expect(tokenDetails).to.deep.equal(expectedTokenDetails); - }); - - it('only migrates once', async () => { - await migrateOldDatabase('1234567890'); - const tokenDetails = await migrateOldDatabase('1234567890'); - - expect(tokenDetails).to.be.null; - }); - - it('does not get a value that has a different sender ID', async () => { - const tokenDetails = await migrateOldDatabase('321321321'); - expect(tokenDetails).to.be.null; - }); - }); - - describe('version 4', () => { - beforeEach(async () => { - const v4TokenDetails: V4TokenDetails = { - createTime: 1234567890, - fcmToken: 'token-value', - swScope: '/scope-value', - vapidKey: base64ToArray('dmFwaWQta2V5LXZhbHVl'), - fcmSenderId: '1234567890', - auth: base64ToArray('YXV0aC12YWx1ZQ'), - p256dh: base64ToArray('cDI1Ni12YWx1ZQ'), - endpoint: 'https://example.org' - }; - - await put(4, v4TokenDetails); - }); - - it('can get a value from old DB', async () => { - const tokenDetails = await migrateOldDatabase('1234567890'); - - const expectedTokenDetails = getFakeTokenDetails(); - - expect(tokenDetails).to.deep.equal(expectedTokenDetails); - }); - - it('only migrates once', async () => { - await migrateOldDatabase('1234567890'); - const tokenDetails = await migrateOldDatabase('1234567890'); - - expect(tokenDetails).to.be.null; - }); - - it('does not get a value that has a different sender ID', async () => { - const tokenDetails = await migrateOldDatabase('321321321'); - expect(tokenDetails).to.be.null; - }); - }); -}); - -async function put(version: number, value: object): Promise { - const db = await openDb('fcm_token_details_db', version, upgradeDb => { - if (upgradeDb.oldVersion === 0) { - const objectStore = upgradeDb.createObjectStore( - 'fcm_token_object_Store', - { - keyPath: 'swScope' - } - ); - objectStore.createIndex('fcmSenderId', 'fcmSenderId', { - unique: false - }); - objectStore.createIndex('fcmToken', 'fcmToken', { unique: true }); - } - }); - - try { - const tx = db.transaction('fcm_token_object_Store', 'readwrite'); - await tx.objectStore('fcm_token_object_Store').put(value); - await tx.complete; - } finally { - db.close(); - } -} diff --git a/packages-exp/messaging-exp/src/helpers/migrate-old-database.ts b/packages-exp/messaging-exp/src/helpers/migrate-old-database.ts deleted file mode 100644 index 40fdece6171..00000000000 --- a/packages-exp/messaging-exp/src/helpers/migrate-old-database.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { deleteDb, openDb } from 'idb'; - -import { TokenDetails } from '../interfaces/token-details'; -import { arrayToBase64 } from './array-base64-translator'; - -// https://github.com/firebase/firebase-js-sdk/blob/7857c212f944a2a9eb421fd4cb7370181bc034b5/packages/messaging/src/interfaces/token-details.ts -export interface V2TokenDetails { - fcmToken: string; - swScope: string; - vapidKey: string | Uint8Array; - subscription: PushSubscription; - fcmSenderId: string; - fcmPushSet: string; - createTime?: number; - endpoint?: string; - auth?: string; - p256dh?: string; -} - -// https://github.com/firebase/firebase-js-sdk/blob/6b5b15ce4ea3df5df5df8a8b33a4e41e249c7715/packages/messaging/src/interfaces/token-details.ts -export interface V3TokenDetails { - fcmToken: string; - swScope: string; - vapidKey: Uint8Array; - fcmSenderId: string; - fcmPushSet: string; - endpoint: string; - auth: ArrayBuffer; - p256dh: ArrayBuffer; - createTime: number; -} - -// https://github.com/firebase/firebase-js-sdk/blob/9567dba664732f681fa7fe60f5b7032bb1daf4c9/packages/messaging/src/interfaces/token-details.ts -export interface V4TokenDetails { - fcmToken: string; - swScope: string; - vapidKey: Uint8Array; - fcmSenderId: string; - endpoint: string; - auth: ArrayBufferLike; - p256dh: ArrayBufferLike; - createTime: number; -} - -const OLD_DB_NAME = 'fcm_token_details_db'; -/** - * The last DB version of 'fcm_token_details_db' was 4. This is one higher, so that the upgrade - * callback is called for all versions of the old DB. - */ -const OLD_DB_VERSION = 5; -const OLD_OBJECT_STORE_NAME = 'fcm_token_object_Store'; - -export async function migrateOldDatabase( - senderId: string -): Promise { - if ('databases' in indexedDB) { - // indexedDb.databases() is an IndexedDB v3 API and does not exist in all browsers. TODO: Remove - // typecast when it lands in TS types. - const databases = await (indexedDB as { - databases(): Promise>; - }).databases(); - const dbNames = databases.map(db => db.name); - - if (!dbNames.includes(OLD_DB_NAME)) { - // old DB didn't exist, no need to open. - return null; - } - } - - let tokenDetails: TokenDetails | null = null; - - const db = await openDb(OLD_DB_NAME, OLD_DB_VERSION, async db => { - if (db.oldVersion < 2) { - // Database too old, skip migration. - return; - } - - if (!db.objectStoreNames.contains(OLD_OBJECT_STORE_NAME)) { - // Database did not exist. Nothing to do. - return; - } - - const objectStore = db.transaction.objectStore(OLD_OBJECT_STORE_NAME); - const value = await objectStore.index('fcmSenderId').get(senderId); - await objectStore.clear(); - - if (!value) { - // No entry in the database, nothing to migrate. - return; - } - - if (db.oldVersion === 2) { - const oldDetails = value as V2TokenDetails; - - if (!oldDetails.auth || !oldDetails.p256dh || !oldDetails.endpoint) { - return; - } - - tokenDetails = { - token: oldDetails.fcmToken, - createTime: oldDetails.createTime ?? Date.now(), - subscriptionOptions: { - auth: oldDetails.auth, - p256dh: oldDetails.p256dh, - endpoint: oldDetails.endpoint, - swScope: oldDetails.swScope, - vapidKey: - typeof oldDetails.vapidKey === 'string' - ? oldDetails.vapidKey - : arrayToBase64(oldDetails.vapidKey) - } - }; - } else if (db.oldVersion === 3) { - const oldDetails = value as V3TokenDetails; - - tokenDetails = { - token: oldDetails.fcmToken, - createTime: oldDetails.createTime, - subscriptionOptions: { - auth: arrayToBase64(oldDetails.auth), - p256dh: arrayToBase64(oldDetails.p256dh), - endpoint: oldDetails.endpoint, - swScope: oldDetails.swScope, - vapidKey: arrayToBase64(oldDetails.vapidKey) - } - }; - } else if (db.oldVersion === 4) { - const oldDetails = value as V4TokenDetails; - - tokenDetails = { - token: oldDetails.fcmToken, - createTime: oldDetails.createTime, - subscriptionOptions: { - auth: arrayToBase64(oldDetails.auth), - p256dh: arrayToBase64(oldDetails.p256dh), - endpoint: oldDetails.endpoint, - swScope: oldDetails.swScope, - vapidKey: arrayToBase64(oldDetails.vapidKey) - } - }; - } - }); - db.close(); - - // Delete all old databases. - await deleteDb(OLD_DB_NAME); - await deleteDb('fcm_vapid_details_db'); - await deleteDb('undefined'); - - return checkTokenDetails(tokenDetails) ? tokenDetails : null; -} - -function checkTokenDetails( - tokenDetails: TokenDetails | null -): tokenDetails is TokenDetails { - if (!tokenDetails || !tokenDetails.subscriptionOptions) { - return false; - } - const { subscriptionOptions } = tokenDetails; - return ( - typeof tokenDetails.createTime === 'number' && - tokenDetails.createTime > 0 && - typeof tokenDetails.token === 'string' && - tokenDetails.token.length > 0 && - typeof subscriptionOptions.auth === 'string' && - subscriptionOptions.auth.length > 0 && - typeof subscriptionOptions.p256dh === 'string' && - subscriptionOptions.p256dh.length > 0 && - typeof subscriptionOptions.endpoint === 'string' && - subscriptionOptions.endpoint.length > 0 && - typeof subscriptionOptions.swScope === 'string' && - subscriptionOptions.swScope.length > 0 && - typeof subscriptionOptions.vapidKey === 'string' && - subscriptionOptions.vapidKey.length > 0 - ); -} diff --git a/packages-exp/messaging-exp/src/helpers/sleep.test.ts b/packages-exp/messaging-exp/src/helpers/sleep.test.ts deleted file mode 100644 index b7c4e228f10..00000000000 --- a/packages-exp/messaging-exp/src/helpers/sleep.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import { SinonFakeTimers, useFakeTimers } from 'sinon'; - -import { expect } from 'chai'; -import { sleep } from './sleep'; - -describe('sleep', () => { - let clock: SinonFakeTimers; - - beforeEach(() => { - clock = useFakeTimers({ shouldAdvanceTime: true }); - }); - - it('returns a promise that resolves after a given amount of time', async () => { - const t0 = clock.now; - await sleep(100); - const t1 = clock.now; - - expect(t1 - t0).to.equal(100); - }); -}); diff --git a/packages-exp/messaging-exp/src/helpers/sleep.ts b/packages-exp/messaging-exp/src/helpers/sleep.ts deleted file mode 100644 index 2bd1eb9283b..00000000000 --- a/packages-exp/messaging-exp/src/helpers/sleep.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** Returns a promise that resolves after given time passes. */ -export function sleep(ms: number): Promise { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -} diff --git a/packages-exp/messaging-exp/src/index.ts b/packages-exp/messaging-exp/src/index.ts deleted file mode 100644 index 42e00bcc420..00000000000 --- a/packages-exp/messaging-exp/src/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Firebase Cloud Messaging - * - * @packageDocumentation - */ - -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '@firebase/installations-exp'; - -import { Messaging } from './interfaces/public-types'; -import { registerMessagingInWindow } from './helpers/register'; - -export { - getToken, - deleteToken, - onMessage, - getMessagingInWindow as getMessaging -} from './api'; -export { isWindowSupported as isSupported } from './api/isSupported'; -export * from './interfaces/public-types'; - -declare module '@firebase/component' { - interface NameServiceMapping { - 'messaging-exp': Messaging; - } -} - -registerMessagingInWindow(); diff --git a/packages-exp/messaging-exp/src/interfaces/app-config.ts b/packages-exp/messaging-exp/src/interfaces/app-config.ts deleted file mode 100644 index 4a887eeb3cc..00000000000 --- a/packages-exp/messaging-exp/src/interfaces/app-config.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export interface AppConfig { - readonly appName: string; - readonly projectId: string; - readonly apiKey: string; - readonly appId: string; - /** Only used for old DB migration. */ - readonly senderId: string; -} diff --git a/packages-exp/messaging-exp/src/interfaces/internal-dependencies.ts b/packages-exp/messaging-exp/src/interfaces/internal-dependencies.ts deleted file mode 100644 index 6d88720ef46..00000000000 --- a/packages-exp/messaging-exp/src/interfaces/internal-dependencies.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AppConfig } from './app-config'; -import { FirebaseAnalyticsInternalName } from '@firebase/analytics-interop-types'; -import { FirebaseApp } from '@firebase/app-exp'; -import { Provider } from '@firebase/component'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; - -export interface FirebaseInternalDependencies { - app: FirebaseApp; - appConfig: AppConfig; - installations: _FirebaseInstallationsInternal; - analyticsProvider: Provider; -} diff --git a/packages-exp/messaging-exp/src/interfaces/internal-message-payload.ts b/packages-exp/messaging-exp/src/interfaces/internal-message-payload.ts deleted file mode 100644 index 940982a912c..00000000000 --- a/packages-exp/messaging-exp/src/interfaces/internal-message-payload.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -import { - CONSOLE_CAMPAIGN_ANALYTICS_ENABLED, - CONSOLE_CAMPAIGN_ID, - CONSOLE_CAMPAIGN_NAME, - CONSOLE_CAMPAIGN_TIME -} from '../util/constants'; - -export interface MessagePayloadInternal { - notification?: NotificationPayloadInternal; - data?: unknown; - fcmOptions?: FcmOptionsInternal; - messageType?: MessageType; - isFirebaseMessaging?: boolean; - from: string; - // eslint-disable-next-line camelcase - collapse_key: string; - // eslint-disable-next-line camelcase - fcm_message_id: string; -} - -export interface NotificationPayloadInternal extends NotificationOptions { - title: string; - // Supported in the Legacy Send API. - // See:https://firebase.google.com/docs/cloud-messaging/xmpp-server-ref. - // eslint-disable-next-line camelcase - click_action?: string; -} - -// Defined in -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#webpushfcmoptions. Note -// that the keys are sent to the clients in snake cases which we need to convert to camel so it can -// be exposed as a type to match the Firebase API convention. -export interface FcmOptionsInternal { - link?: string; - - // eslint-disable-next-line camelcase - analytics_label?: string; -} - -export enum MessageType { - PUSH_RECEIVED = 'push-received', - NOTIFICATION_CLICKED = 'notification-clicked' -} - -/** Additional data of a message sent from the FN Console. */ -export interface ConsoleMessageData { - [CONSOLE_CAMPAIGN_ID]: string; - [CONSOLE_CAMPAIGN_TIME]: string; - [CONSOLE_CAMPAIGN_NAME]?: string; - [CONSOLE_CAMPAIGN_ANALYTICS_ENABLED]?: '1'; -} diff --git a/packages-exp/messaging-exp/src/interfaces/token-details.ts b/packages-exp/messaging-exp/src/interfaces/token-details.ts deleted file mode 100644 index 791c94d267b..00000000000 --- a/packages-exp/messaging-exp/src/interfaces/token-details.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export interface TokenDetails { - token: string; - createTime: number; - /** Does not exist in Safari since it's not using Push API. */ - subscriptionOptions?: SubscriptionOptions; -} - -/** - * Additional options and values required by a Push API subscription. - */ -export interface SubscriptionOptions { - vapidKey: string; - swScope: string; - endpoint: string; - auth: string; - p256dh: string; -} diff --git a/packages-exp/messaging-exp/src/testing/compare-headers.test.ts b/packages-exp/messaging-exp/src/testing/compare-headers.test.ts deleted file mode 100644 index ad171740ace..00000000000 --- a/packages-exp/messaging-exp/src/testing/compare-headers.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import './setup'; - -import { AssertionError, expect } from 'chai'; - -import { compareHeaders } from './compare-headers'; - -describe('compareHeaders', () => { - it("doesn't fail if headers contain the same entries", () => { - const headers1 = new Headers({ a: '123', b: '456' }); - const headers2 = new Headers({ a: '123', b: '456' }); - compareHeaders(headers1, headers2); - }); - - it('fails if headers contain different keys', () => { - const headers1 = new Headers({ a: '123', b: '456', extraKey: '789' }); - const headers2 = new Headers({ a: '123', b: '456' }); - expect(() => { - compareHeaders(headers1, headers2); - }).to.throw(AssertionError); - }); - - it('fails if headers contain different values', () => { - const headers1 = new Headers({ a: '123', b: '456' }); - const headers2 = new Headers({ a: '123', b: 'differentValue' }); - expect(() => { - compareHeaders(headers1, headers2); - }).to.throw(AssertionError); - }); -}); diff --git a/packages-exp/messaging-exp/src/testing/compare-headers.ts b/packages-exp/messaging-exp/src/testing/compare-headers.ts deleted file mode 100644 index 6f760caf32d..00000000000 --- a/packages-exp/messaging-exp/src/testing/compare-headers.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import './setup'; - -import { expect } from 'chai'; - -// Trick TS since it's set to target ES5. -declare class HeadersWithEntries extends Headers { - entries?(): Iterable<[string, string]>; -} - -// Chai doesn't check if Headers objects contain the same entries, so we need to do that manually. -export function compareHeaders( - expectedHeaders: HeadersWithEntries, - actualHeaders: HeadersWithEntries -): void { - const expected = makeMap(expectedHeaders); - const actual = makeMap(actualHeaders); - expect(actual).to.deep.equal(expected); -} - -function makeMap(headers: HeadersWithEntries): Map { - expect(headers.entries).not.to.be.undefined; - return new Map(headers.entries!()); -} diff --git a/packages-exp/messaging-exp/src/testing/fakes/firebase-dependencies.ts b/packages-exp/messaging-exp/src/testing/fakes/firebase-dependencies.ts deleted file mode 100644 index 683b7b30e22..00000000000 --- a/packages-exp/messaging-exp/src/testing/fakes/firebase-dependencies.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - FirebaseAnalyticsInternal, - FirebaseAnalyticsInternalName -} from '@firebase/analytics-interop-types'; - -import { FirebaseInternalDependencies } from '../../interfaces/internal-dependencies'; -import { FirebaseOptions } from '@firebase/app-exp'; -import { Provider } from '@firebase/component'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; -import { extractAppConfig } from '../../helpers/extract-app-config'; - -export function getFakeFirebaseDependencies( - options: FirebaseOptions = {} -): FirebaseInternalDependencies { - const app = getFakeApp(options); - return { - app, - appConfig: extractAppConfig(app), - installations: getFakeInstallations(), - analyticsProvider: getFakeAnalyticsProvider() - }; -} - -export function getFakeApp(options: FirebaseOptions = {}): any { - options = { - apiKey: 'apiKey', - projectId: 'projectId', - authDomain: 'authDomain', - messagingSenderId: '1234567890', - databaseURL: 'databaseUrl', - storageBucket: 'storageBucket', - appId: '1:777777777777:web:d93b5ca1475efe57', - ...options - }; - return { - name: 'appName', - options, - automaticDataCollectionEnabled: true, - delete: async () => {}, - messaging: (() => null as unknown) as any, - installations: () => getFakeInstallations() - }; -} - -export function getFakeInstallations(): _FirebaseInstallationsInternal { - return { - getId: async () => 'FID', - getToken: async () => 'authToken' - }; -} - -export function getFakeAnalyticsProvider(): Provider { - const analytics: FirebaseAnalyticsInternal = { - logEvent() {} - }; - - return ({ - get: async () => analytics, - getImmediate: () => analytics - } as unknown) as Provider; -} diff --git a/packages-exp/messaging-exp/src/testing/fakes/service-worker.ts b/packages-exp/messaging-exp/src/testing/fakes/service-worker.ts deleted file mode 100644 index 8cc09811e0e..00000000000 --- a/packages-exp/messaging-exp/src/testing/fakes/service-worker.ts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Client, - Clients, - ExtendableEvent, - ServiceWorkerGlobalScope, - WindowClient -} from '../../util/sw-types'; - -import { Writable } from 'ts-essentials'; - -// Add fake SW types. -declare const self: Window & Writable; - -// When trying to stub self.clients self.registration, Sinon complains that these properties do not -// exist. This is because we're not actually running these tests in a service worker context. - -// Here we're adding placeholders for Sinon to overwrite, which prevents the "Cannot stub -// non-existent own property" errors. - -// Casting to any is needed because TS also thinks that we're in a SW context and considers these -// properties readonly. - -// Missing function types are implemented from interfaces, so types are actually defined. -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - -// const originalSwRegistration = ServiceWorkerRegistration; -export function mockServiceWorker(): void { - self.clients = new FakeClients(); - self.registration = new FakeServiceWorkerRegistration(); -} - -export function restoreServiceWorker(): void { - self.clients = new FakeClients(); - self.registration = new FakeServiceWorkerRegistration(); -} - -class FakeClients implements Clients { - private readonly clients: Client[] = []; - - async get(id: string) { - return this.clients.find(c => id === c.id) ?? null; - } - - async matchAll({ type = 'all' } = {}) { - if (type === 'all') { - return this.clients; - } - return this.clients.filter(c => c.type === type); - } - - async openWindow(url: string) { - const windowClient = new FakeWindowClient(); - windowClient.url = url; - this.clients.push(windowClient); - return windowClient; - } - - async claim() {} -} - -let currentId = 0; -class FakeWindowClient implements WindowClient { - readonly id: string; - readonly type = 'window'; - focused = false; - visibilityState: VisibilityState = 'hidden'; - url = 'https://example.org'; - - constructor() { - this.id = (currentId++).toString(); - } - - async focus() { - this.focused = true; - return this; - } - - async navigate(url: string) { - this.url = url; - return this; - } - - postMessage() {} -} - -export class FakeServiceWorkerRegistration - implements ServiceWorkerRegistration { - active = null; - installing = null; - waiting = null; - onupdatefound = null; - pushManager = new FakePushManager(); - scope = '/scope-value'; - - // Unused in FCM Web SDK, no need to mock these. - navigationPreload = (null as unknown) as NavigationPreloadManager; - sync = (null as unknown) as SyncManager; - updateViaCache = (null as unknown) as ServiceWorkerUpdateViaCache; - - async getNotifications() { - return []; - } - - async showNotification() {} - - async update() {} - - async unregister() { - return true; - } - - addEventListener() {} - removeEventListener() {} - dispatchEvent() { - return true; - } -} - -class FakePushManager implements PushManager { - private subscription: FakePushSubscription | null = null; - - async permissionState() { - return 'granted' as const; - } - - async getSubscription() { - return this.subscription; - } - - async subscribe() { - if (!this.subscription) { - this.subscription = new FakePushSubscription(); - } - return this.subscription!; - } -} - -export class FakePushSubscription implements PushSubscription { - endpoint = 'https://example.org'; - expirationTime = 1234567890; - auth = 'auth-value'; // Encoded: 'YXV0aC12YWx1ZQ' - p256 = 'p256-value'; // Encoded: 'cDI1Ni12YWx1ZQ' - - getKey(name: PushEncryptionKeyName) { - const encoder = new TextEncoder(); - return encoder.encode(name === 'auth' ? this.auth : this.p256); - } - - async unsubscribe() { - return true; - } - - // Unused in FCM - toJSON = (null as unknown) as () => PushSubscriptionJSON; - options = (null as unknown) as PushSubscriptionOptions; -} - -/** - * Most of the fields in here are unused / deprecated. They are only added here to match the TS - * Event interface. - */ -export class FakeEvent implements ExtendableEvent { - NONE = Event.NONE; - AT_TARGET = Event.AT_TARGET; - BUBBLING_PHASE = Event.BUBBLING_PHASE; - CAPTURING_PHASE = Event.CAPTURING_PHASE; - bubbles: boolean; - cancelable: boolean; - composed: boolean; - timeStamp = 123456; - isTrusted = true; - eventPhase = Event.NONE; - target = null; - currentTarget = null; - srcElement = null; - cancelBubble = false; - defaultPrevented = false; - returnValue = false; - - preventDefault() { - this.defaultPrevented = true; - this.returnValue = true; - } - - stopPropagation() {} - - stopImmediatePropagation() {} - - initEvent() {} - - waitUntil() {} - - composedPath() { - return []; - } - - constructor(public type: string, options: EventInit = {}) { - this.bubbles = options.bubbles ?? false; - this.cancelable = options.cancelable ?? false; - this.composed = options.composed ?? false; - } -} diff --git a/packages-exp/messaging-exp/src/testing/fakes/token-details.ts b/packages-exp/messaging-exp/src/testing/fakes/token-details.ts deleted file mode 100644 index 73eea06b2e5..00000000000 --- a/packages-exp/messaging-exp/src/testing/fakes/token-details.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FakePushSubscription } from './service-worker'; -import { TokenDetails } from '../../interfaces/token-details'; -import { arrayToBase64 } from '../../helpers/array-base64-translator'; - -export function getFakeTokenDetails(): TokenDetails { - const subscription = new FakePushSubscription(); - - return { - token: 'token-value', - createTime: 1234567890, - subscriptionOptions: { - swScope: '/scope-value', - vapidKey: arrayToBase64(new TextEncoder().encode('vapid-key-value')), // 'dmFwaWQta2V5LXZhbHVl', - endpoint: subscription.endpoint, - auth: arrayToBase64(subscription.getKey('auth')), // 'YXV0aC12YWx1ZQ' - p256dh: arrayToBase64(subscription.getKey('p256dh')) // 'cDI1Ni12YWx1ZQ' - } - }; -} diff --git a/packages-exp/messaging-exp/src/testing/setup.ts b/packages-exp/messaging-exp/src/testing/setup.ts deleted file mode 100644 index 61b3524ca74..00000000000 --- a/packages-exp/messaging-exp/src/testing/setup.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as chaiAsPromised from 'chai-as-promised'; -import * as sinonChai from 'sinon-chai'; - -import { dbDelete } from '../internals/idb-manager'; -import { deleteDb } from 'idb'; -import { restore } from 'sinon'; -import { use } from 'chai'; - -use(chaiAsPromised); -use(sinonChai); - -afterEach(async () => { - restore(); - await dbDelete(); - await deleteDb('fcm_token_details_db'); -}); diff --git a/packages-exp/messaging-exp/src/testing/sinon-types.ts b/packages-exp/messaging-exp/src/testing/sinon-types.ts deleted file mode 100644 index 13eafbac969..00000000000 --- a/packages-exp/messaging-exp/src/testing/sinon-types.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { SinonSpy, SinonStub } from 'sinon'; - -// Helper types for Sinon stubs and spies. - -export type Stub any> = SinonStub< - Parameters, - ReturnType ->; - -export type Spy any> = SinonSpy< - Parameters, - ReturnType ->; diff --git a/packages-exp/messaging-exp/src/util/constants.ts b/packages-exp/messaging-exp/src/util/constants.ts deleted file mode 100644 index 8491380a5a0..00000000000 --- a/packages-exp/messaging-exp/src/util/constants.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const DEFAULT_SW_PATH = '/firebase-messaging-sw.js'; -export const DEFAULT_SW_SCOPE = '/firebase-cloud-messaging-push-scope'; - -export const DEFAULT_VAPID_KEY = - 'BDOU99-h67HcA6JeFXHbSNMu7e2yNNu3RzoMj8TM4W88jITfq7ZmPvIM1Iv-4_l2LxQcYwhqby2xGpWwzjfAnG4'; - -export const ENDPOINT = 'https://fcmregistrations.googleapis.com/v1'; - -/** Key of FCM Payload in Notification's data field. */ -export const FCM_MSG = 'FCM_MSG'; - -export const CONSOLE_CAMPAIGN_ID = 'google.c.a.c_id'; -export const CONSOLE_CAMPAIGN_NAME = 'google.c.a.c_l'; -export const CONSOLE_CAMPAIGN_TIME = 'google.c.a.ts'; -/** Set to '1' if Analytics is enabled for the campaign */ -export const CONSOLE_CAMPAIGN_ANALYTICS_ENABLED = 'google.c.a.e'; -export const TAG = 'FirebaseMessaging: '; -export const MAX_NUMBER_OF_EVENTS_PER_LOG_REQUEST = 1000; -export const MAX_RETRIES = 3; -export const LOG_INTERVAL_IN_MS = 86400000; //24 hour -export const DEFAULT_BACKOFF_TIME_MS = 5000; - -// FCM log source name registered at Firelog: 'FCM_CLIENT_EVENT_LOGGING'. It uniquely identifies -// FCM's logging configuration. -export const FCM_LOG_SOURCE = 1249; - -// Defined as in proto/messaging_event.proto. Neglecting fields that are supported. -export const SDK_PLATFORM_WEB = 3; -export const EVENT_MESSAGE_DELIVERED = 1; - -export enum MessageType { - DATA_MESSAGE = 1, - DISPLAY_NOTIFICATION = 3 -} diff --git a/packages-exp/messaging-exp/src/util/errors.ts b/packages-exp/messaging-exp/src/util/errors.ts deleted file mode 100644 index 14b001b6dc2..00000000000 --- a/packages-exp/messaging-exp/src/util/errors.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorFactory, ErrorMap } from '@firebase/util'; - -export const enum ErrorCode { - MISSING_APP_CONFIG_VALUES = 'missing-app-config-values', - AVAILABLE_IN_WINDOW = 'only-available-in-window', - AVAILABLE_IN_SW = 'only-available-in-sw', - PERMISSION_DEFAULT = 'permission-default', - PERMISSION_BLOCKED = 'permission-blocked', - UNSUPPORTED_BROWSER = 'unsupported-browser', - INDEXED_DB_UNSUPPORTED = 'indexed-db-unsupported', - FAILED_DEFAULT_REGISTRATION = 'failed-service-worker-registration', - TOKEN_SUBSCRIBE_FAILED = 'token-subscribe-failed', - TOKEN_SUBSCRIBE_NO_TOKEN = 'token-subscribe-no-token', - TOKEN_UNSUBSCRIBE_FAILED = 'token-unsubscribe-failed', - TOKEN_UPDATE_FAILED = 'token-update-failed', - TOKEN_UPDATE_NO_TOKEN = 'token-update-no-token', - INVALID_BG_HANDLER = 'invalid-bg-handler', - USE_SW_AFTER_GET_TOKEN = 'use-sw-after-get-token', - INVALID_SW_REGISTRATION = 'invalid-sw-registration', - USE_VAPID_KEY_AFTER_GET_TOKEN = 'use-vapid-key-after-get-token', - INVALID_VAPID_KEY = 'invalid-vapid-key' -} - -export const ERROR_MAP: ErrorMap = { - [ErrorCode.MISSING_APP_CONFIG_VALUES]: - 'Missing App configuration value: "{$valueName}"', - [ErrorCode.AVAILABLE_IN_WINDOW]: - 'This method is available in a Window context.', - [ErrorCode.AVAILABLE_IN_SW]: - 'This method is available in a service worker context.', - [ErrorCode.PERMISSION_DEFAULT]: - 'The notification permission was not granted and dismissed instead.', - [ErrorCode.PERMISSION_BLOCKED]: - 'The notification permission was not granted and blocked instead.', - [ErrorCode.UNSUPPORTED_BROWSER]: - "This browser doesn't support the API's required to use the firebase SDK.", - [ErrorCode.INDEXED_DB_UNSUPPORTED]: - "This browser doesn't support indexedDb.open() (ex. Safari iFrame, Firefox Private Browsing, etc)", - [ErrorCode.FAILED_DEFAULT_REGISTRATION]: - 'We are unable to register the default service worker. {$browserErrorMessage}', - [ErrorCode.TOKEN_SUBSCRIBE_FAILED]: - 'A problem occurred while subscribing the user to FCM: {$errorInfo}', - [ErrorCode.TOKEN_SUBSCRIBE_NO_TOKEN]: - 'FCM returned no token when subscribing the user to push.', - [ErrorCode.TOKEN_UNSUBSCRIBE_FAILED]: - 'A problem occurred while unsubscribing the ' + - 'user from FCM: {$errorInfo}', - [ErrorCode.TOKEN_UPDATE_FAILED]: - 'A problem occurred while updating the user from FCM: {$errorInfo}', - [ErrorCode.TOKEN_UPDATE_NO_TOKEN]: - 'FCM returned no token when updating the user to push.', - [ErrorCode.USE_SW_AFTER_GET_TOKEN]: - 'The useServiceWorker() method may only be called once and must be ' + - 'called before calling getToken() to ensure your service worker is used.', - [ErrorCode.INVALID_SW_REGISTRATION]: - 'The input to useServiceWorker() must be a ServiceWorkerRegistration.', - [ErrorCode.INVALID_BG_HANDLER]: - 'The input to setBackgroundMessageHandler() must be a function.', - [ErrorCode.INVALID_VAPID_KEY]: 'The public VAPID key must be a string.', - [ErrorCode.USE_VAPID_KEY_AFTER_GET_TOKEN]: - 'The usePublicVapidKey() method may only be called once and must be ' + - 'called before calling getToken() to ensure your VAPID key is used.' -}; - -interface ErrorParams { - [ErrorCode.MISSING_APP_CONFIG_VALUES]: { - valueName: string; - }; - [ErrorCode.FAILED_DEFAULT_REGISTRATION]: { browserErrorMessage: string }; - [ErrorCode.TOKEN_SUBSCRIBE_FAILED]: { errorInfo: string }; - [ErrorCode.TOKEN_UNSUBSCRIBE_FAILED]: { errorInfo: string }; - [ErrorCode.TOKEN_UPDATE_FAILED]: { errorInfo: string }; -} - -export const ERROR_FACTORY = new ErrorFactory( - 'messaging', - 'Messaging', - ERROR_MAP -); diff --git a/packages-exp/messaging-exp/src/util/sw-types.ts b/packages-exp/messaging-exp/src/util/sw-types.ts deleted file mode 100644 index 587cc248f50..00000000000 --- a/packages-exp/messaging-exp/src/util/sw-types.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Subset of Web Worker types from lib.webworker.d.ts - * https://github.com/Microsoft/TypeScript/blob/master/lib/lib.webworker.d.ts - * - * Since it's not possible to have both "dom" and "webworker" libs in a single project, we have to - * manually declare the web worker types we need. - */ - -/* eslint-disable @typescript-eslint/no-explicit-any */ // These types are from TS - -// Not the whole interface, just the parts we're currently using. If TS claims that something does -// not exist on this, feel free to add it. -export interface ServiceWorkerGlobalScope { - readonly location: WorkerLocation; - readonly clients: Clients; - readonly registration: ServiceWorkerRegistration; - addEventListener( - type: K, - listener: ( - this: ServiceWorkerGlobalScope, - ev: ServiceWorkerGlobalScopeEventMap[K] - ) => any, - options?: boolean | AddEventListenerOptions - ): void; -} - -// Same as the previous interface -export interface ServiceWorkerGlobalScopeEventMap { - notificationclick: NotificationEvent; - push: PushEvent; - pushsubscriptionchange: PushSubscriptionChangeEvent; -} - -export interface Client { - readonly id: string; - readonly type: ClientTypes; - readonly url: string; - postMessage(message: any, transfer?: Transferable[]): void; -} - -export interface ClientQueryOptions { - includeReserved?: boolean; - includeUncontrolled?: boolean; - type?: ClientTypes; -} - -export interface WindowClient extends Client { - readonly focused: boolean; - readonly visibilityState: VisibilityState; - focus(): Promise; - navigate(url: string): Promise; -} - -export interface Clients { - claim(): Promise; - get(id: string): Promise; - matchAll(options?: ClientQueryOptions): Promise; - openWindow(url: string): Promise; -} - -export interface ExtendableEvent extends Event { - waitUntil(f: Promise): void; -} - -export interface NotificationEvent extends ExtendableEvent { - readonly action: string; - readonly notification: Notification; -} - -interface PushMessageData { - arrayBuffer(): ArrayBuffer; - blob(): Blob; - json(): any; - text(): string; -} - -export interface PushEvent extends ExtendableEvent { - readonly data: PushMessageData | null; -} - -export interface PushSubscriptionChangeEvent extends ExtendableEvent { - readonly newSubscription: PushSubscription | null; - readonly oldSubscription: PushSubscription | null; -} - -interface WorkerLocation { - readonly hash: string; - readonly host: string; - readonly hostname: string; - readonly href: string; - readonly origin: string; - readonly pathname: string; - readonly port: string; - readonly protocol: string; - readonly search: string; - toString(): string; -} diff --git a/packages-exp/messaging-exp/tsconfig.json b/packages-exp/messaging-exp/tsconfig.json deleted file mode 100644 index 4b63b47c5b5..00000000000 --- a/packages-exp/messaging-exp/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "noUnusedLocals": true, - "lib": [ - "dom", - "es2017" - ], - "downlevelIteration": true - }, - "exclude": [ - "dist/**/*" - ] -} diff --git a/packages-exp/performance-compat/.eslintrc.js b/packages-exp/performance-compat/.eslintrc.js deleted file mode 100644 index ca80aa0f69a..00000000000 --- a/packages-exp/performance-compat/.eslintrc.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = { - extends: '../../config/.eslintrc.js', - parserOptions: { - project: 'tsconfig.json', - // to make vscode-eslint work with monorepo - // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 - tsconfigRootDir: __dirname - } -}; diff --git a/packages-exp/performance-compat/rollup.config.js b/packages-exp/performance-compat/rollup.config.js deleted file mode 100644 index 4df9d8a2f48..00000000000 --- a/packages-exp/performance-compat/rollup.config.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared.js'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/performance-compat/rollup.config.release.js b/packages-exp/performance-compat/rollup.config.release.js deleted file mode 100644 index b0b1dc5154a..00000000000 --- a/packages-exp/performance-compat/rollup.config.release.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared.js'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/performance-exp/README.md b/packages-exp/performance-exp/README.md deleted file mode 100644 index 6193537a9a5..00000000000 --- a/packages-exp/performance-exp/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @firebase/performance-exp - -This is the Firebase Performance component of the Firebase JS SDK. - -**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/performance-exp/karma.conf.js b/packages-exp/performance-exp/karma.conf.js deleted file mode 100644 index 7f333fdb2fd..00000000000 --- a/packages-exp/performance-exp/karma.conf.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const karmaBase = require('../../config/karma.base'); - -const files = [`test/**/*`, 'src/**/*.test.ts']; - -module.exports = function (config) { - config.set({ - ...karmaBase, - // files to load into karma - files, - preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha'] - }); -}; - -module.exports.files = files; diff --git a/packages-exp/performance-exp/package.json b/packages-exp/performance-exp/package.json deleted file mode 100644 index 4aa6ae7edc8..00000000000 --- a/packages-exp/performance-exp/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "@firebase/performance-exp", - "version": "0.0.900", - "description": "Firebase performance for web", - "author": "Firebase (https://firebase.google.com/)", - "private": true, - "main": "dist/index.cjs.js", - "browser": "dist/index.esm2017.js", - "module": "dist/index.esm2017.js", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:deps": "lerna run --scope @firebase/performance-exp --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js", - "dev": "rollup -c -w", - "test": "run-p lint test:browser", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", - "test:browser": "karma start --single-run", - "test:debug": "karma start --browsers=Chrome --auto-watch", - "prettier": "prettier --write '{src,test}/**/*.{js,ts}'", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc" - }, - "peerDependencies": { - "@firebase/app-exp": "0.x" - }, - "dependencies": { - "@firebase/logger": "0.2.6", - "@firebase/installations-exp": "0.0.900", - "@firebase/util": "1.3.0", - "@firebase/component": "0.5.6", - "tslib": "^2.1.0" - }, - "license": "Apache-2.0", - "devDependencies": { - "@firebase/app-exp": "0.0.900", - "rollup": "2.52.2", - "@rollup/plugin-json": "4.1.0", - "rollup-plugin-typescript2": "0.30.0", - "typescript": "4.2.2" - }, - "repository": { - "directory": "packages/performance-exp", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "typings": "dist/src/index.d.ts", - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - }, - "esm5": "dist/index.esm.js" -} \ No newline at end of file diff --git a/packages-exp/performance-exp/rollup.config.js b/packages-exp/performance-exp/rollup.config.js deleted file mode 100644 index b5221c26179..00000000000 --- a/packages-exp/performance-exp/rollup.config.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import pkg from './package.json'; -import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared'; - -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); - -/** - * ES5 Builds - */ -const es5BuildPlugins = [typescriptPlugin({ typescript }), json()]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/performance-exp/rollup.config.release.js b/packages-exp/performance-exp/rollup.config.release.js deleted file mode 100644 index a217c6990b0..00000000000 --- a/packages-exp/performance-exp/rollup.config.release.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: ['@firebase/installations'] - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: ['@firebase/installations'] - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/performance-exp/rollup.shared.js b/packages-exp/performance-exp/rollup.shared.js deleted file mode 100644 index 461af4f05ad..00000000000 --- a/packages-exp/performance-exp/rollup.shared.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -/** - * ES5 Builds - */ -export const es5BuildsNoPlugin = [ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -export const es2017BuildsNoPlugin = [ - { - input: 'src/index.ts', - output: [{ file: pkg.browser, format: 'es', sourcemap: true }], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/performance-exp/src/constants.ts b/packages-exp/performance-exp/src/constants.ts deleted file mode 100644 index 2cac126da97..00000000000 --- a/packages-exp/performance-exp/src/constants.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { version } from '../package.json'; - -export const SDK_VERSION = version; -/** The prefix for start User Timing marks used for creating Traces. */ -export const TRACE_START_MARK_PREFIX = 'FB-PERF-TRACE-START'; -/** The prefix for stop User Timing marks used for creating Traces. */ -export const TRACE_STOP_MARK_PREFIX = 'FB-PERF-TRACE-STOP'; -/** The prefix for User Timing measure used for creating Traces. */ -export const TRACE_MEASURE_PREFIX = 'FB-PERF-TRACE-MEASURE'; -/** The prefix for out of the box page load Trace name. */ -export const OOB_TRACE_PAGE_LOAD_PREFIX = '_wt_'; - -export const FIRST_PAINT_COUNTER_NAME = '_fp'; - -export const FIRST_CONTENTFUL_PAINT_COUNTER_NAME = '_fcp'; - -export const FIRST_INPUT_DELAY_COUNTER_NAME = '_fid'; - -export const CONFIG_LOCAL_STORAGE_KEY = '@firebase/performance/config'; - -export const CONFIG_EXPIRY_LOCAL_STORAGE_KEY = - '@firebase/performance/configexpire'; - -export const SERVICE = 'performance'; -export const SERVICE_NAME = 'Performance'; diff --git a/packages-exp/performance-exp/src/controllers/perf.test.ts b/packages-exp/performance-exp/src/controllers/perf.test.ts deleted file mode 100644 index 369fabe12ce..00000000000 --- a/packages-exp/performance-exp/src/controllers/perf.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { stub } from 'sinon'; -import { PerformanceController } from '../controllers/perf'; -import { Api, setupApi } from '../services/api_service'; -import * as initializationService from '../services/initialization_service'; -import { SettingsService } from '../services/settings_service'; -import { consoleLogger } from '../utils/console_logger'; -import { FirebaseApp } from '@firebase/app-exp'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; -import '../../test/setup'; - -describe('Firebase Performance Test', () => { - setupApi(window); - - const fakeFirebaseConfig = { - apiKey: 'api-key', - authDomain: 'project-id.firebaseapp.com', - databaseURL: 'https://project-id.firebaseio.com', - projectId: 'project-id', - storageBucket: 'project-id.appspot.com', - messagingSenderId: 'sender-id', - appId: '1:111:web:a1234' - }; - - const fakeFirebaseApp = ({ - options: fakeFirebaseConfig - } as unknown) as FirebaseApp; - - const fakeInstallations = ({} as unknown) as _FirebaseInstallationsInternal; - - describe('#constructor', () => { - it('does not initialize performance if the required apis are not available', () => { - stub(Api.prototype, 'requiredApisAvailable').returns(false); - stub(initializationService, 'getInitializationPromise'); - stub(consoleLogger, 'info'); - const performanceController = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - performanceController._init(); - - expect(initializationService.getInitializationPromise).not.be.called; - expect(consoleLogger.info).to.be.calledWithMatch( - /.*Fetch.*Promise.*cookies.*/ - ); - }); - }); - - describe('#settings', () => { - it('applies the settings if provided', async () => { - const settings = { - instrumentationEnabled: false, - dataCollectionEnabled: false - }; - - const performance = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - performance._init(settings); - - expect(performance.instrumentationEnabled).is.equal(false); - expect(performance.dataCollectionEnabled).is.equal(false); - }); - - it('uses defaults when settings are not provided', async () => { - const expectedInstrumentationEnabled = SettingsService.getInstance() - .instrumentationEnabled; - const expectedDataCollectionEnabled = SettingsService.getInstance() - .dataCollectionEnabled; - - const performance = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - performance._init(); - - expect(performance.instrumentationEnabled).is.equal( - expectedInstrumentationEnabled - ); - expect(performance.dataCollectionEnabled).is.equal( - expectedDataCollectionEnabled - ); - }); - - describe('#instrumentationEnabled', () => { - it('sets instrumentationEnabled to enabled', async () => { - const performance = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - performance._init(); - - performance.instrumentationEnabled = true; - expect(performance.instrumentationEnabled).is.equal(true); - }); - - it('sets instrumentationEnabled to disabled', async () => { - const performance = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - performance._init(); - - performance.instrumentationEnabled = false; - expect(performance.instrumentationEnabled).is.equal(false); - }); - }); - - describe('#dataCollectionEnabled', () => { - it('sets dataCollectionEnabled to enabled', async () => { - const performance = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - performance._init(); - - performance.dataCollectionEnabled = true; - expect(performance.dataCollectionEnabled).is.equal(true); - }); - - it('sets dataCollectionEnabled to disabled', () => { - const performance = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - performance._init(); - - performance.dataCollectionEnabled = false; - expect(performance.dataCollectionEnabled).is.equal(false); - }); - }); - }); -}); diff --git a/packages-exp/performance-exp/src/controllers/perf.ts b/packages-exp/performance-exp/src/controllers/perf.ts deleted file mode 100644 index 5f2a70c0bc4..00000000000 --- a/packages-exp/performance-exp/src/controllers/perf.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { setupOobResources } from '../services/oob_resources_service'; -import { SettingsService } from '../services/settings_service'; -import { getInitializationPromise } from '../services/initialization_service'; -import { Api } from '../services/api_service'; -import { FirebaseApp } from '@firebase/app-exp'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; -import { PerformanceSettings, FirebasePerformance } from '../public_types'; -import { validateIndexedDBOpenable } from '@firebase/util'; -import { setupTransportService } from '../services/transport_service'; -import { consoleLogger } from '../utils/console_logger'; - -export class PerformanceController implements FirebasePerformance { - private initialized: boolean = false; - - constructor( - readonly app: FirebaseApp, - readonly installations: _FirebaseInstallationsInternal - ) {} - - /** - * This method *must* be called internally as part of creating a - * PerformanceController instance. - * - * Currently it's not possible to pass the settings object through the - * constructor using Components, so this method exists to be called with the - * desired settings, to ensure nothing is collected without the user's - * consent. - */ - _init(settings?: PerformanceSettings): void { - if (this.initialized) { - return; - } - - if (settings?.dataCollectionEnabled !== undefined) { - this.dataCollectionEnabled = settings.dataCollectionEnabled; - } - if (settings?.instrumentationEnabled !== undefined) { - this.instrumentationEnabled = settings.instrumentationEnabled; - } - - if (Api.getInstance().requiredApisAvailable()) { - validateIndexedDBOpenable() - .then(isAvailable => { - if (isAvailable) { - setupTransportService(); - getInitializationPromise(this).then( - () => setupOobResources(this), - () => setupOobResources(this) - ); - this.initialized = true; - } - }) - .catch(error => { - consoleLogger.info(`Environment doesn't support IndexedDB: ${error}`); - }); - } else { - consoleLogger.info( - 'Firebase Performance cannot start if the browser does not support ' + - '"Fetch" and "Promise", or cookies are disabled.' - ); - } - } - - set instrumentationEnabled(val: boolean) { - SettingsService.getInstance().instrumentationEnabled = val; - } - get instrumentationEnabled(): boolean { - return SettingsService.getInstance().instrumentationEnabled; - } - - set dataCollectionEnabled(val: boolean) { - SettingsService.getInstance().dataCollectionEnabled = val; - } - get dataCollectionEnabled(): boolean { - return SettingsService.getInstance().dataCollectionEnabled; - } -} diff --git a/packages-exp/performance-exp/src/resources/network_request.test.ts b/packages-exp/performance-exp/src/resources/network_request.test.ts deleted file mode 100644 index 20b6598f3f9..00000000000 --- a/packages-exp/performance-exp/src/resources/network_request.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stub, restore } from 'sinon'; -import { createNetworkRequestEntry } from '../../src/resources/network_request'; -import { expect } from 'chai'; -import { Api, setupApi } from '../services/api_service'; -import * as perfLogger from '../services/perf_logger'; - -import { FirebaseApp } from '@firebase/app-exp'; -import { PerformanceController } from '../controllers/perf'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import '../../test/setup'; - -describe('Firebase Performance > network_request', () => { - setupApi(window); - - const fakeFirebaseApp = ({ - options: {} - } as unknown) as FirebaseApp; - - const fakeInstallations = ({} as unknown) as FirebaseInstallations; - const performanceController = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - - beforeEach(() => { - stub(Api.prototype, 'getTimeOrigin').returns(1528521843799.5032); - stub(perfLogger, 'logNetworkRequest'); - }); - - afterEach(() => { - restore(); - }); - - describe('#createNetworkRequestEntry', () => { - it('logs network request when all required fields present', () => { - const PERFORMANCE_ENTRY = ({ - name: 'http://some.test.website.com', - transferSize: 500, - startTime: 1645352.632345, - responseStart: 1645360.244323, - responseEnd: 1645360.832443 - } as unknown) as PerformanceResourceTiming; - - const EXPECTED_NETWORK_REQUEST = { - performanceController, - url: 'http://some.test.website.com', - responsePayloadBytes: 500, - startTimeUs: 1528523489152135, - timeToResponseInitiatedUs: 7611, - timeToResponseCompletedUs: 8200 - }; - - createNetworkRequestEntry(performanceController, PERFORMANCE_ENTRY); - - expect( - (perfLogger.logNetworkRequest as any).calledWith( - EXPECTED_NETWORK_REQUEST - ) - ).to.be.true; - }); - - it('doesnt log network request when responseStart is absent', () => { - const PERFORMANCE_ENTRY = ({ - name: 'http://some.test.website.com', - transferSize: 500, - startTime: 1645352.632345, - responseEnd: 1645360.832443 - } as unknown) as PerformanceResourceTiming; - - createNetworkRequestEntry(performanceController, PERFORMANCE_ENTRY); - - expect(perfLogger.logNetworkRequest).to.not.have.been.called; - }); - }); -}); diff --git a/packages-exp/performance-exp/src/resources/network_request.ts b/packages-exp/performance-exp/src/resources/network_request.ts deleted file mode 100644 index c5a8103eb03..00000000000 --- a/packages-exp/performance-exp/src/resources/network_request.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Api } from '../services/api_service'; -import { logNetworkRequest } from '../services/perf_logger'; -import { PerformanceController } from '../controllers/perf'; - -// The order of values of this enum should not be changed. -export const enum HttpMethod { - HTTP_METHOD_UNKNOWN = 0, - GET = 1, - PUT = 2, - POST = 3, - DELETE = 4, - HEAD = 5, - PATCH = 6, - OPTIONS = 7, - TRACE = 8, - CONNECT = 9 -} - -// Durations are in microseconds. -export interface NetworkRequest { - performanceController: PerformanceController; - url: string; - httpMethod?: HttpMethod; - requestPayloadBytes?: number; - responsePayloadBytes?: number; - httpResponseCode?: number; - responseContentType?: string; - startTimeUs?: number; - timeToRequestCompletedUs?: number; - timeToResponseInitiatedUs?: number; - timeToResponseCompletedUs?: number; -} - -export function createNetworkRequestEntry( - performanceController: PerformanceController, - entry: PerformanceEntry -): void { - const performanceEntry = entry as PerformanceResourceTiming; - if (!performanceEntry || performanceEntry.responseStart === undefined) { - return; - } - const timeOrigin = Api.getInstance().getTimeOrigin(); - const startTimeUs = Math.floor( - (performanceEntry.startTime + timeOrigin) * 1000 - ); - const timeToResponseInitiatedUs = performanceEntry.responseStart - ? Math.floor( - (performanceEntry.responseStart - performanceEntry.startTime) * 1000 - ) - : undefined; - const timeToResponseCompletedUs = Math.floor( - (performanceEntry.responseEnd - performanceEntry.startTime) * 1000 - ); - // Remove the query params from logged network request url. - const url = performanceEntry.name && performanceEntry.name.split('?')[0]; - const networkRequest: NetworkRequest = { - performanceController, - url, - responsePayloadBytes: performanceEntry.transferSize, - startTimeUs, - timeToResponseInitiatedUs, - timeToResponseCompletedUs - }; - - logNetworkRequest(networkRequest); -} diff --git a/packages-exp/performance-exp/src/resources/trace.test.ts b/packages-exp/performance-exp/src/resources/trace.test.ts deleted file mode 100644 index c3d2f9c61db..00000000000 --- a/packages-exp/performance-exp/src/resources/trace.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { spy, stub, restore } from 'sinon'; -import { Trace } from '../resources/trace'; -import { expect } from 'chai'; -import { Api, setupApi } from '../services/api_service'; -import * as perfLogger from '../services/perf_logger'; -import { PerformanceController } from '../controllers/perf'; -import { FirebaseApp } from '@firebase/app-exp'; -import { FirebaseInstallations } from '@firebase/installations-types'; - -import '../../test/setup'; - -describe('Firebase Performance > trace', () => { - setupApi(window); - const fakeFirebaseConfig = { - apiKey: 'api-key', - authDomain: 'project-id.firebaseapp.com', - databaseURL: 'https://project-id.firebaseio.com', - projectId: 'project-id', - storageBucket: 'project-id.appspot.com', - messagingSenderId: 'sender-id', - appId: '1:111:web:a1234' - }; - - const fakeFirebaseApp = ({ - options: fakeFirebaseConfig - } as unknown) as FirebaseApp; - - const fakeInstallations = ({} as unknown) as FirebaseInstallations; - const performanceController = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - - let trace: Trace; - const createTrace = (): Trace => { - return new Trace(performanceController, 'test'); - }; - - beforeEach(() => { - spy(Api.prototype, 'mark'); - stub(perfLogger, 'logTrace'); - trace = createTrace(); - }); - - afterEach(() => { - restore(); - }); - - describe('#start', () => { - beforeEach(() => { - trace.start(); - }); - - it('uses the underlying api method', () => { - expect(Api.getInstance().mark).to.be.calledOnce; - }); - - it('throws if a trace is started twice', () => { - expect(() => trace.start()).to.throw(); - }); - }); - - describe('#stop', () => { - it('adds a mark to the performance timeline', () => { - trace.start(); - trace.stop(); - - expect(Api.getInstance().mark).to.be.calledTwice; - }); - - it('logs the trace', () => { - trace.start(); - trace.stop(); - - expect((perfLogger.logTrace as any).calledOnceWith(trace)).to.be.true; - }); - }); - - describe('#record', () => { - it('logs a custom trace with non-positive start time value', () => { - expect(() => trace.record(0, 20)).to.throw(); - expect(() => trace.record(-100, 20)).to.throw(); - }); - - it('logs a custom trace with non-positive duration value', () => { - expect(() => trace.record(1000, 0)).to.throw(); - expect(() => trace.record(1000, -200)).to.throw(); - }); - - it('logs a trace without metrics or custom attributes', () => { - trace.record(1, 20); - - expect((perfLogger.logTrace as any).calledOnceWith(trace)).to.be.true; - }); - - it('logs a trace with metrics', () => { - trace.record(1, 20, { metrics: { cacheHits: 1 } }); - - expect((perfLogger.logTrace as any).calledOnceWith(trace)).to.be.true; - expect(trace.getMetric('cacheHits')).to.eql(1); - }); - - it('logs a trace with custom attributes', () => { - trace.record(1, 20, { attributes: { level: '1' } }); - - expect((perfLogger.logTrace as any).calledOnceWith(trace)).to.be.true; - expect(trace.getAttributes()).to.eql({ level: '1' }); - }); - - it('logs a trace with custom attributes and metrics', () => { - trace.record(1, 20, { - attributes: { level: '1' }, - metrics: { cacheHits: 1 } - }); - - expect((perfLogger.logTrace as any).calledOnceWith(trace)).to.be.true; - expect(trace.getAttributes()).to.eql({ level: '1' }); - expect(trace.getMetric('cacheHits')).to.eql(1); - }); - }); - - describe('#incrementMetric', () => { - it('creates new metric if one doesnt exist.', () => { - trace.incrementMetric('cacheHits', 200); - - expect(trace.getMetric('cacheHits')).to.eql(200); - }); - - it('increments metric if it already exists.', () => { - trace.incrementMetric('cacheHits', 200); - trace.incrementMetric('cacheHits', 400); - - expect(trace.getMetric('cacheHits')).to.eql(600); - }); - - it('increments metric value as an integer even if the value is provided in float.', () => { - trace.incrementMetric('cacheHits', 200); - trace.incrementMetric('cacheHits', 400.38); - - expect(trace.getMetric('cacheHits')).to.eql(600); - }); - - it('increments metric value with a negative float.', () => { - trace.incrementMetric('cacheHits', 200); - trace.incrementMetric('cacheHits', -230.38); - - expect(trace.getMetric('cacheHits')).to.eql(-31); - }); - - it('throws error if metric doesnt exist and has invalid name', () => { - expect(() => trace.incrementMetric('_invalidMetric', 1)).to.throw(); - }); - }); - - describe('#putMetric', () => { - it('creates new metric if one doesnt exist and has valid name.', () => { - trace.putMetric('cacheHits', 200); - - expect(trace.getMetric('cacheHits')).to.eql(200); - }); - - it('sets the metric value as an integer even if the value is provided in float.', () => { - trace.putMetric('timelapse', 200.48); - - expect(trace.getMetric('timelapse')).to.eql(200); - }); - - it('replaces metric if it already exists.', () => { - trace.putMetric('cacheHits', 200); - trace.putMetric('cacheHits', 400); - - expect(trace.getMetric('cacheHits')).to.eql(400); - }); - - it('throws error if metric doesnt exist and has invalid name', () => { - expect(() => trace.putMetric('_invalidMetric', 1)).to.throw(); - expect(() => trace.putMetric('_fid', 1)).to.throw(); - }); - }); - - describe('#getMetric', () => { - it('returns 0 if metric doesnt exist', () => { - expect(trace.getMetric('doesThisExist')).to.equal(0); - }); - - it('returns 0 if it exists and equals 0', () => { - trace.putMetric('cacheHits', 0); - - expect(trace.getMetric('cacheHits')).to.equal(0); - }); - - it('returns metric if it exists', () => { - trace.putMetric('cacheHits', 200); - - expect(trace.getMetric('cacheHits')).to.equal(200); - }); - - it('returns multiple metrics if they exist', () => { - trace.putMetric('cacheHits', 200); - trace.putMetric('bytesDownloaded', 25); - - expect(trace.getMetric('cacheHits')).to.equal(200); - expect(trace.getMetric('bytesDownloaded')).to.equal(25); - }); - }); - - describe('#putAttribute', () => { - it('creates new attribute if it doesnt exist', () => { - trace.putAttribute('level', '4'); - - expect(trace.getAttributes()).to.eql({ level: '4' }); - }); - - it('replaces attribute if it exists', () => { - trace.putAttribute('level', '4'); - trace.putAttribute('level', '7'); - - expect(trace.getAttributes()).to.eql({ level: '7' }); - }); - - it('throws error if attribute name is invalid', () => { - expect(() => trace.putAttribute('_invalidAttribute', '1')).to.throw(); - }); - - it('throws error if attribute value is invalid', () => { - const longAttributeValue = - 'too-long-attribute-value-over-one-hundred-characters-too-long-attribute-value-over-one-' + - 'hundred-charac'; - expect(() => - trace.putAttribute('validName', longAttributeValue) - ).to.throw(); - }); - }); - - describe('#getAttribute', () => { - it('returns undefined for attribute that doesnt exist', () => { - expect(trace.getAttribute('level')).to.be.undefined; - }); - - it('returns attribute if it exists', () => { - trace.putAttribute('level', '4'); - expect(trace.getAttribute('level')).to.equal('4'); - }); - - it('returns separate attributes if they exist', () => { - trace.putAttribute('level', '4'); - trace.putAttribute('stage', 'beginning'); - - expect(trace.getAttribute('level')).to.equal('4'); - expect(trace.getAttribute('stage')).to.equal('beginning'); - }); - }); - - describe('#removeAttribute', () => { - it('does not throw if removing attribute that doesnt exist', () => { - expect(() => trace.removeAttribute('doesNotExist')).to.not.throw; - }); - - it('removes attribute if it exists', () => { - trace.putAttribute('level', '4'); - expect(trace.getAttribute('level')).to.equal('4'); - - trace.removeAttribute('level'); - expect(trace.getAttribute('level')).to.be.undefined; - }); - - it('retains other attributes', () => { - trace.putAttribute('level', '4'); - trace.putAttribute('stage', 'beginning'); - - trace.removeAttribute('level'); - expect(trace.getAttribute('level')).to.be.undefined; - expect(trace.getAttribute('stage')).to.equal('beginning'); - }); - }); -}); diff --git a/packages-exp/performance-exp/src/resources/trace.ts b/packages-exp/performance-exp/src/resources/trace.ts deleted file mode 100644 index 67958572984..00000000000 --- a/packages-exp/performance-exp/src/resources/trace.ts +++ /dev/null @@ -1,356 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - TRACE_START_MARK_PREFIX, - TRACE_STOP_MARK_PREFIX, - TRACE_MEASURE_PREFIX, - OOB_TRACE_PAGE_LOAD_PREFIX, - FIRST_PAINT_COUNTER_NAME, - FIRST_CONTENTFUL_PAINT_COUNTER_NAME, - FIRST_INPUT_DELAY_COUNTER_NAME -} from '../constants'; -import { Api } from '../services/api_service'; -import { logTrace } from '../services/perf_logger'; -import { ERROR_FACTORY, ErrorCode } from '../utils/errors'; -import { - isValidCustomAttributeName, - isValidCustomAttributeValue -} from '../utils/attributes_utils'; -import { - isValidMetricName, - convertMetricValueToInteger -} from '../utils/metric_utils'; -import { PerformanceTrace } from '../public_types'; -import { PerformanceController } from '../controllers/perf'; - -const enum TraceState { - UNINITIALIZED = 1, - RUNNING, - TERMINATED -} - -export class Trace implements PerformanceTrace { - private state: TraceState = TraceState.UNINITIALIZED; - startTimeUs!: number; - durationUs!: number; - private customAttributes: { [key: string]: string } = {}; - counters: { [counterName: string]: number } = {}; - private api = Api.getInstance(); - private randomId = Math.floor(Math.random() * 1000000); - private traceStartMark!: string; - private traceStopMark!: string; - private traceMeasure!: string; - - /** - * @param performanceController The performance controller running. - * @param name The name of the trace. - * @param isAuto If the trace is auto-instrumented. - * @param traceMeasureName The name of the measure marker in user timing specification. This field - * is only set when the trace is built for logging when the user directly uses the user timing - * api (performance.mark and performance.measure). - */ - constructor( - readonly performanceController: PerformanceController, - readonly name: string, - readonly isAuto = false, - traceMeasureName?: string - ) { - if (!this.isAuto) { - this.traceStartMark = `${TRACE_START_MARK_PREFIX}-${this.randomId}-${this.name}`; - this.traceStopMark = `${TRACE_STOP_MARK_PREFIX}-${this.randomId}-${this.name}`; - this.traceMeasure = - traceMeasureName || - `${TRACE_MEASURE_PREFIX}-${this.randomId}-${this.name}`; - - if (traceMeasureName) { - // For the case of direct user timing traces, no start stop will happen. The measure object - // is already available. - this.calculateTraceMetrics(); - } - } - } - - /** - * Starts a trace. The measurement of the duration starts at this point. - */ - start(): void { - if (this.state !== TraceState.UNINITIALIZED) { - throw ERROR_FACTORY.create(ErrorCode.TRACE_STARTED_BEFORE, { - traceName: this.name - }); - } - this.api.mark(this.traceStartMark); - this.state = TraceState.RUNNING; - } - - /** - * Stops the trace. The measurement of the duration of the trace stops at this point and trace - * is logged. - */ - stop(): void { - if (this.state !== TraceState.RUNNING) { - throw ERROR_FACTORY.create(ErrorCode.TRACE_STOPPED_BEFORE, { - traceName: this.name - }); - } - this.state = TraceState.TERMINATED; - this.api.mark(this.traceStopMark); - this.api.measure( - this.traceMeasure, - this.traceStartMark, - this.traceStopMark - ); - this.calculateTraceMetrics(); - logTrace(this); - } - - /** - * Records a trace with predetermined values. If this method is used a trace is created and logged - * directly. No need to use start and stop methods. - * @param startTime Trace start time since epoch in millisec - * @param duration The duraction of the trace in millisec - * @param options An object which can optionally hold maps of custom metrics and custom attributes - */ - record( - startTime: number, - duration: number, - options?: { - metrics?: { [key: string]: number }; - attributes?: { [key: string]: string }; - } - ): void { - if (startTime <= 0) { - throw ERROR_FACTORY.create(ErrorCode.NONPOSITIVE_TRACE_START_TIME, { - traceName: this.name - }); - } - if (duration <= 0) { - throw ERROR_FACTORY.create(ErrorCode.NONPOSITIVE_TRACE_DURATION, { - traceName: this.name - }); - } - - this.durationUs = Math.floor(duration * 1000); - this.startTimeUs = Math.floor(startTime * 1000); - if (options && options.attributes) { - this.customAttributes = { ...options.attributes }; - } - if (options && options.metrics) { - for (const metric of Object.keys(options.metrics)) { - if (!isNaN(Number(options.metrics[metric]))) { - this.counters[metric] = Number(Math.floor(options.metrics[metric])); - } - } - } - logTrace(this); - } - - /** - * Increments a custom metric by a certain number or 1 if number not specified. Will create a new - * custom metric if one with the given name does not exist. The value will be floored down to an - * integer. - * @param counter Name of the custom metric - * @param numAsInteger Increment by value - */ - incrementMetric(counter: string, numAsInteger = 1): void { - if (this.counters[counter] === undefined) { - this.putMetric(counter, numAsInteger); - } else { - this.putMetric(counter, this.counters[counter] + numAsInteger); - } - } - - /** - * Sets a custom metric to a specified value. Will create a new custom metric if one with the - * given name does not exist. The value will be floored down to an integer. - * @param counter Name of the custom metric - * @param numAsInteger Set custom metric to this value - */ - putMetric(counter: string, numAsInteger: number): void { - if (isValidMetricName(counter, this.name)) { - this.counters[counter] = convertMetricValueToInteger(numAsInteger); - } else { - throw ERROR_FACTORY.create(ErrorCode.INVALID_CUSTOM_METRIC_NAME, { - customMetricName: counter - }); - } - } - - /** - * Returns the value of the custom metric by that name. If a custom metric with that name does - * not exist will return zero. - * @param counter - */ - getMetric(counter: string): number { - return this.counters[counter] || 0; - } - - /** - * Sets a custom attribute of a trace to a certain value. - * @param attr - * @param value - */ - putAttribute(attr: string, value: string): void { - const isValidName = isValidCustomAttributeName(attr); - const isValidValue = isValidCustomAttributeValue(value); - if (isValidName && isValidValue) { - this.customAttributes[attr] = value; - return; - } - // Throw appropriate error when the attribute name or value is invalid. - if (!isValidName) { - throw ERROR_FACTORY.create(ErrorCode.INVALID_ATTRIBUTE_NAME, { - attributeName: attr - }); - } - if (!isValidValue) { - throw ERROR_FACTORY.create(ErrorCode.INVALID_ATTRIBUTE_VALUE, { - attributeValue: value - }); - } - } - - /** - * Retrieves the value a custom attribute of a trace is set to. - * @param attr - */ - getAttribute(attr: string): string | undefined { - return this.customAttributes[attr]; - } - - removeAttribute(attr: string): void { - if (this.customAttributes[attr] === undefined) { - return; - } - delete this.customAttributes[attr]; - } - - getAttributes(): { [key: string]: string } { - return { ...this.customAttributes }; - } - - private setStartTime(startTime: number): void { - this.startTimeUs = startTime; - } - - private setDuration(duration: number): void { - this.durationUs = duration; - } - - /** - * Calculates and assigns the duration and start time of the trace using the measure performance - * entry. - */ - private calculateTraceMetrics(): void { - const perfMeasureEntries = this.api.getEntriesByName(this.traceMeasure); - const perfMeasureEntry = perfMeasureEntries && perfMeasureEntries[0]; - if (perfMeasureEntry) { - this.durationUs = Math.floor(perfMeasureEntry.duration * 1000); - this.startTimeUs = Math.floor( - (perfMeasureEntry.startTime + this.api.getTimeOrigin()) * 1000 - ); - } - } - - /** - * @param navigationTimings A single element array which contains the navigationTIming object of - * the page load - * @param paintTimings A array which contains paintTiming object of the page load - * @param firstInputDelay First input delay in millisec - */ - static createOobTrace( - performanceController: PerformanceController, - navigationTimings: PerformanceNavigationTiming[], - paintTimings: PerformanceEntry[], - firstInputDelay?: number - ): void { - const route = Api.getInstance().getUrl(); - if (!route) { - return; - } - const trace = new Trace( - performanceController, - OOB_TRACE_PAGE_LOAD_PREFIX + route, - true - ); - const timeOriginUs = Math.floor(Api.getInstance().getTimeOrigin() * 1000); - trace.setStartTime(timeOriginUs); - - // navigationTimings includes only one element. - if (navigationTimings && navigationTimings[0]) { - trace.setDuration(Math.floor(navigationTimings[0].duration * 1000)); - trace.putMetric( - 'domInteractive', - Math.floor(navigationTimings[0].domInteractive * 1000) - ); - trace.putMetric( - 'domContentLoadedEventEnd', - Math.floor(navigationTimings[0].domContentLoadedEventEnd * 1000) - ); - trace.putMetric( - 'loadEventEnd', - Math.floor(navigationTimings[0].loadEventEnd * 1000) - ); - } - - const FIRST_PAINT = 'first-paint'; - const FIRST_CONTENTFUL_PAINT = 'first-contentful-paint'; - if (paintTimings) { - const firstPaint = paintTimings.find( - paintObject => paintObject.name === FIRST_PAINT - ); - if (firstPaint && firstPaint.startTime) { - trace.putMetric( - FIRST_PAINT_COUNTER_NAME, - Math.floor(firstPaint.startTime * 1000) - ); - } - const firstContentfulPaint = paintTimings.find( - paintObject => paintObject.name === FIRST_CONTENTFUL_PAINT - ); - if (firstContentfulPaint && firstContentfulPaint.startTime) { - trace.putMetric( - FIRST_CONTENTFUL_PAINT_COUNTER_NAME, - Math.floor(firstContentfulPaint.startTime * 1000) - ); - } - - if (firstInputDelay) { - trace.putMetric( - FIRST_INPUT_DELAY_COUNTER_NAME, - Math.floor(firstInputDelay * 1000) - ); - } - } - - logTrace(trace); - } - - static createUserTimingTrace( - performanceController: PerformanceController, - measureName: string - ): void { - const trace = new Trace( - performanceController, - measureName, - false, - measureName - ); - logTrace(trace); - } -} diff --git a/packages-exp/performance-exp/src/services/api_service.test.ts b/packages-exp/performance-exp/src/services/api_service.test.ts deleted file mode 100644 index c0e9a29b690..00000000000 --- a/packages-exp/performance-exp/src/services/api_service.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stub } from 'sinon'; -import { expect } from 'chai'; -import { Api, setupApi } from './api_service'; -import '../../test/setup'; - -describe('Firebase Performance > api_service', () => { - const PAGE_URL = 'http://www.test.com/abcd?a=2'; - const PERFORMANCE_ENTRY: PerformanceEntry = { - duration: 0, - entryType: 'paint', - name: 'first-contentful-paint', - startTime: 149.01000005193055, - toJSON: () => {} - }; - - const mockWindow = { ...self }; - // hack for IE11. self.hasOwnProperty('performance') returns false in IE11 - mockWindow.performance = self.performance; - - let api: Api; - - beforeEach(() => { - stub(mockWindow.performance, 'mark'); - stub(mockWindow.performance, 'measure'); - stub(mockWindow.performance, 'getEntriesByType').returns([ - PERFORMANCE_ENTRY - ]); - stub(mockWindow.performance, 'getEntriesByName').returns([ - PERFORMANCE_ENTRY - ]); - // This is to make sure the test page is not changed by changing the href of location object. - mockWindow.location = { ...self.location, href: PAGE_URL }; - - setupApi(mockWindow); - api = Api.getInstance(); - }); - - describe('getUrl', () => { - it('removes the query params', () => { - expect(api.getUrl()).to.equal('http://www.test.com/abcd'); - }); - }); - - describe('mark', () => { - it('creates performance mark', () => { - const MARK_NAME = 'mark1'; - api.mark(MARK_NAME); - - expect(mockWindow.performance.mark).to.be.calledOnceWith(MARK_NAME); - }); - }); - - describe('measure', () => { - it('creates a performance measure', () => { - const MEASURE_NAME = 'measure1'; - const MARK_1_NAME = 'mark1'; - const MARK_2_NAME = 'mark2'; - api.measure(MEASURE_NAME, MARK_1_NAME, MARK_2_NAME); - - expect(mockWindow.performance.measure).to.be.calledOnceWith( - MEASURE_NAME, - MARK_1_NAME, - MARK_2_NAME - ); - }); - }); - - describe('getEntriesByType', () => { - it('calls the underlying performance api', () => { - expect(api.getEntriesByType('paint')).to.deep.equal([PERFORMANCE_ENTRY]); - }); - - it('does not throw if the browser does not include underlying api', () => { - api = new Api(({ performance: undefined } as unknown) as Window); - - expect(() => { - api.getEntriesByType('paint'); - }).to.not.throw(); - expect(api.getEntriesByType('paint')).to.deep.equal([]); - }); - }); - - describe('getEntriesByName', () => { - it('calls the underlying performance api', () => { - expect(api.getEntriesByName('paint')).to.deep.equal([PERFORMANCE_ENTRY]); - }); - - it('does not throw if the browser does not include underlying api', () => { - api = new Api(({ performance: undefined } as any) as Window); - - expect(() => { - api.getEntriesByName('paint'); - }).to.not.throw(); - expect(api.getEntriesByName('paint')).to.deep.equal([]); - }); - }); -}); diff --git a/packages-exp/performance-exp/src/services/api_service.ts b/packages-exp/performance-exp/src/services/api_service.ts deleted file mode 100644 index f500e415e87..00000000000 --- a/packages-exp/performance-exp/src/services/api_service.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ERROR_FACTORY, ErrorCode } from '../utils/errors'; -import { isIndexedDBAvailable } from '@firebase/util'; -import { consoleLogger } from '../utils/console_logger'; - -declare global { - interface Window { - PerformanceObserver: typeof PerformanceObserver; - perfMetrics?: { onFirstInputDelay(fn: (fid: number) => void): void }; - } -} - -let apiInstance: Api | undefined; -let windowInstance: Window | undefined; - -export type EntryType = - | 'mark' - | 'measure' - | 'paint' - | 'resource' - | 'frame' - | 'navigation'; - -/** - * This class holds a reference to various browser related objects injected by - * set methods. - */ -export class Api { - private readonly performance: Performance; - /** PreformanceObserver constructor function. */ - private readonly PerformanceObserver: typeof PerformanceObserver; - private readonly windowLocation: Location; - readonly onFirstInputDelay?: (fn: (fid: number) => void) => void; - readonly localStorage?: Storage; - readonly document: Document; - readonly navigator: Navigator; - - constructor(readonly window?: Window) { - if (!window) { - throw ERROR_FACTORY.create(ErrorCode.NO_WINDOW); - } - this.performance = window.performance; - this.PerformanceObserver = window.PerformanceObserver; - this.windowLocation = window.location; - this.navigator = window.navigator; - this.document = window.document; - if (this.navigator && this.navigator.cookieEnabled) { - // If user blocks cookies on the browser, accessing localStorage will - // throw an exception. - this.localStorage = window.localStorage; - } - if (window.perfMetrics && window.perfMetrics.onFirstInputDelay) { - this.onFirstInputDelay = window.perfMetrics.onFirstInputDelay; - } - } - - getUrl(): string { - // Do not capture the string query part of url. - return this.windowLocation.href.split('?')[0]; - } - - mark(name: string): void { - if (!this.performance || !this.performance.mark) { - return; - } - this.performance.mark(name); - } - - measure(measureName: string, mark1: string, mark2: string): void { - if (!this.performance || !this.performance.measure) { - return; - } - this.performance.measure(measureName, mark1, mark2); - } - - getEntriesByType(type: EntryType): PerformanceEntry[] { - if (!this.performance || !this.performance.getEntriesByType) { - return []; - } - return this.performance.getEntriesByType(type); - } - - getEntriesByName(name: string): PerformanceEntry[] { - if (!this.performance || !this.performance.getEntriesByName) { - return []; - } - return this.performance.getEntriesByName(name); - } - - getTimeOrigin(): number { - // Polyfill the time origin with performance.timing.navigationStart. - return ( - this.performance && - (this.performance.timeOrigin || this.performance.timing.navigationStart) - ); - } - - requiredApisAvailable(): boolean { - if ( - !fetch || - !Promise || - !this.navigator || - !this.navigator.cookieEnabled - ) { - consoleLogger.info( - 'Firebase Performance cannot start if browser does not support fetch and Promise or cookie is disabled.' - ); - return false; - } - - if (!isIndexedDBAvailable()) { - consoleLogger.info('IndexedDB is not supported by current browswer'); - return false; - } - return true; - } - - setupObserver( - entryType: EntryType, - callback: (entry: PerformanceEntry) => void - ): void { - if (!this.PerformanceObserver) { - return; - } - const observer = new this.PerformanceObserver(list => { - for (const entry of list.getEntries()) { - // `entry` is a PerformanceEntry instance. - callback(entry); - } - }); - - // Start observing the entry types you care about. - observer.observe({ entryTypes: [entryType] }); - } - - static getInstance(): Api { - if (apiInstance === undefined) { - apiInstance = new Api(windowInstance); - } - return apiInstance; - } -} - -export function setupApi(window: Window): void { - windowInstance = window; -} diff --git a/packages-exp/performance-exp/src/services/iid_service.test.ts b/packages-exp/performance-exp/src/services/iid_service.test.ts deleted file mode 100644 index 3f1e195506e..00000000000 --- a/packages-exp/performance-exp/src/services/iid_service.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stub } from 'sinon'; -import { expect } from 'chai'; -import { - getIid, - getIidPromise, - getAuthenticationToken, - getAuthTokenPromise -} from './iid_service'; -import '../../test/setup'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; - -describe('Firebase Perofmrance > iid_service', () => { - const IID = 'fid'; - const AUTH_TOKEN = 'authToken'; - - let fakeInstallations: _FirebaseInstallationsInternal; - before(() => { - const getId = stub().resolves(IID); - const getToken = stub().resolves(AUTH_TOKEN); - fakeInstallations = ({ - getId, - getToken - } as unknown) as _FirebaseInstallationsInternal; - }); - - describe('getIidPromise', () => { - it('provides iid', async () => { - const iid = await getIidPromise(fakeInstallations); - - expect(iid).to.be.equal(IID); - expect(getIid()).to.be.equal(IID); - }); - }); - - describe('getAuthTokenPromise', () => { - it('provides authentication token', async () => { - const token = await getAuthTokenPromise(fakeInstallations); - - expect(token).to.be.equal(AUTH_TOKEN); - expect(getAuthenticationToken()).to.be.equal(AUTH_TOKEN); - }); - }); -}); diff --git a/packages-exp/performance-exp/src/services/iid_service.ts b/packages-exp/performance-exp/src/services/iid_service.ts deleted file mode 100644 index 6d336e3c034..00000000000 --- a/packages-exp/performance-exp/src/services/iid_service.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; - -let iid: string | undefined; -let authToken: string | undefined; - -export function getIidPromise( - installationsService: _FirebaseInstallationsInternal -): Promise { - const iidPromise = installationsService.getId(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - iidPromise.then((iidVal: string) => { - iid = iidVal; - }); - return iidPromise; -} - -// This method should be used after the iid is retrieved by getIidPromise method. -export function getIid(): string | undefined { - return iid; -} - -export function getAuthTokenPromise( - installationsService: _FirebaseInstallationsInternal -): Promise { - const authTokenPromise = installationsService.getToken(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - authTokenPromise.then((authTokenVal: string) => { - authToken = authTokenVal; - }); - return authTokenPromise; -} - -export function getAuthenticationToken(): string | undefined { - return authToken; -} diff --git a/packages-exp/performance-exp/src/services/initialization_service.test.ts b/packages-exp/performance-exp/src/services/initialization_service.test.ts deleted file mode 100644 index 242bbdff4a0..00000000000 --- a/packages-exp/performance-exp/src/services/initialization_service.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stub } from 'sinon'; -import { expect } from 'chai'; -import { - getInitializationPromise, - isPerfInitialized -} from './initialization_service'; -import { setupApi } from './api_service'; -import { FirebaseApp } from '@firebase/app-exp'; -import '../../test/setup'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import { PerformanceController } from '../controllers/perf'; - -describe('Firebase Perofmrance > initialization_service', () => { - const IID = 'fid'; - const AUTH_TOKEN = 'authToken'; - const getId = stub(); - const getToken = stub(); - - const fakeFirebaseConfig = { - apiKey: 'api-key', - authDomain: 'project-id.firebaseapp.com', - databaseURL: 'https://project-id.firebaseio.com', - projectId: 'project-id', - storageBucket: 'project-id.appspot.com', - messagingSenderId: 'sender-id', - appId: '1:111:web:a1234' - }; - - const fakeFirebaseApp = ({ - options: fakeFirebaseConfig - } as unknown) as FirebaseApp; - - const fakeInstallations = ({ - getId, - getToken - } as unknown) as FirebaseInstallations; - const performanceController = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - - const mockWindow = { ...self }; - mockWindow.document = { ...mockWindow.document, readyState: 'complete' }; - - beforeEach(() => { - stub(self, 'fetch').resolves(new Response('{}')); - mockWindow.localStorage = { ...mockWindow.localStorage, setItem: stub() }; - - setupApi(mockWindow); - }); - - it('changes initialization status after initialization is done', async () => { - getId.resolves(IID); - getToken.resolves(AUTH_TOKEN); - await getInitializationPromise(performanceController); - - expect(isPerfInitialized()).to.be.true; - }); - - it('returns initilization as not done before promise is resolved', async () => { - getId.resolves(IID); - getToken.resolves(AUTH_TOKEN); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - getInitializationPromise(performanceController); - - expect(isPerfInitialized()).to.be.false; - }); -}); diff --git a/packages-exp/performance-exp/src/services/initialization_service.ts b/packages-exp/performance-exp/src/services/initialization_service.ts deleted file mode 100644 index 94a1dfab1e5..00000000000 --- a/packages-exp/performance-exp/src/services/initialization_service.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getIidPromise } from './iid_service'; -import { getConfig } from './remote_config_service'; -import { Api } from './api_service'; -import { PerformanceController } from '../controllers/perf'; - -const enum InitializationStatus { - notInitialized = 1, - initializationPending, - initialized -} - -let initializationStatus = InitializationStatus.notInitialized; - -let initializationPromise: Promise | undefined; - -export function getInitializationPromise( - performanceController: PerformanceController -): Promise { - initializationStatus = InitializationStatus.initializationPending; - - initializationPromise = - initializationPromise || initializePerf(performanceController); - - return initializationPromise; -} - -export function isPerfInitialized(): boolean { - return initializationStatus === InitializationStatus.initialized; -} - -function initializePerf( - performanceController: PerformanceController -): Promise { - return getDocumentReadyComplete() - .then(() => getIidPromise(performanceController.installations)) - .then(iid => getConfig(performanceController, iid)) - .then( - () => changeInitializationStatus(), - () => changeInitializationStatus() - ); -} - -/** - * Returns a promise which resolves whenever the document readystate is complete or - * immediately if it is called after page load complete. - */ -function getDocumentReadyComplete(): Promise { - const document = Api.getInstance().document; - return new Promise(resolve => { - if (document && document.readyState !== 'complete') { - const handler = (): void => { - if (document.readyState === 'complete') { - document.removeEventListener('readystatechange', handler); - resolve(); - } - }; - document.addEventListener('readystatechange', handler); - } else { - resolve(); - } - }); -} - -function changeInitializationStatus(): void { - initializationStatus = InitializationStatus.initialized; -} diff --git a/packages-exp/performance-exp/src/services/oob_resources_service.test.ts b/packages-exp/performance-exp/src/services/oob_resources_service.test.ts deleted file mode 100644 index c0210b24116..00000000000 --- a/packages-exp/performance-exp/src/services/oob_resources_service.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - spy, - stub, - SinonSpy, - SinonStub, - useFakeTimers, - SinonFakeTimers -} from 'sinon'; -import { expect } from 'chai'; -import { Api, setupApi, EntryType } from './api_service'; -import * as iidService from './iid_service'; -import { setupOobResources } from './oob_resources_service'; -import { Trace } from '../resources/trace'; -import '../../test/setup'; -import { PerformanceController } from '../controllers/perf'; -import { FirebaseApp } from '@firebase/app-exp'; -import { FirebaseInstallations } from '@firebase/installations-types'; - -describe('Firebase Performance > oob_resources_service', () => { - const MOCK_ID = 'idasdfsffe'; - - const NAVIGATION_PERFORMANCE_ENTRY: PerformanceNavigationTiming = { - connectEnd: 2.9499998781830072, - connectStart: 2.9499998781830072, - decodedBodySize: 1519, - domComplete: 186.48499995470047, - domContentLoadedEventEnd: 64.0499999281019, - domContentLoadedEventStart: 62.440000008791685, - domInteractive: 62.42000008933246, - domainLookupEnd: 2.9499998781830072, - domainLookupStart: 2.9499998781830072, - duration: 187.7349999267608, - encodedBodySize: 732, - entryType: 'navigation', - fetchStart: 2.9499998781830072, - initiatorType: 'navigation', - loadEventEnd: 187.7349999267608, - loadEventStart: 187.72999988868833, - name: 'https://test.firebase.com/', - nextHopProtocol: 'h2', - redirectCount: 0, - redirectEnd: 0, - redirectStart: 0, - requestStart: 5.034999921917915, - responseEnd: 9.305000072345138, - responseStart: 8.940000087022781, - secureConnectionStart: 0, - startTime: 0, - transferSize: 1259, - type: 'reload', - unloadEventEnd: 14.870000071823597, - unloadEventStart: 14.870000071823597, - workerStart: 0, - toJSON: () => {} - }; - - const PAINT_PERFORMANCE_ENTRY: PerformanceEntry = { - duration: 0, - entryType: 'paint', - name: 'first-contentful-paint', - startTime: 122.18499998562038, - toJSON: () => {} - }; - - let getIidStub: SinonStub<[], string | undefined>; - let apiGetInstanceSpy: SinonSpy<[], Api>; - let getEntriesByTypeStub: SinonStub<[EntryType], PerformanceEntry[]>; - let setupObserverStub: SinonStub< - [EntryType, (entry: PerformanceEntry) => void], - void - >; - let createOobTraceStub: SinonStub< - [ - PerformanceController, - PerformanceNavigationTiming[], - PerformanceEntry[], - (number | undefined)? - ], - void - >; - let clock: SinonFakeTimers; - - setupApi(self); - - const fakeFirebaseConfig = { - apiKey: 'api-key', - authDomain: 'project-id.firebaseapp.com', - databaseURL: 'https://project-id.firebaseio.com', - projectId: 'project-id', - storageBucket: 'project-id.appspot.com', - messagingSenderId: 'sender-id', - appId: '1:111:web:a1234' - }; - - const fakeFirebaseApp = ({ - options: fakeFirebaseConfig - } as unknown) as FirebaseApp; - - const fakeInstallations = ({} as unknown) as FirebaseInstallations; - const performanceController = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - - beforeEach(() => { - getIidStub = stub(iidService, 'getIid'); - apiGetInstanceSpy = spy(Api, 'getInstance'); - clock = useFakeTimers(); - getEntriesByTypeStub = stub(Api.prototype, 'getEntriesByType').callsFake( - entry => { - if (entry === 'navigation') { - return [NAVIGATION_PERFORMANCE_ENTRY]; - } - return [PAINT_PERFORMANCE_ENTRY]; - } - ); - setupObserverStub = stub(Api.prototype, 'setupObserver'); - createOobTraceStub = stub(Trace, 'createOobTrace'); - }); - - afterEach(() => { - clock.restore(); - }); - - describe('setupOobResources', () => { - it('does not start if there is no iid', () => { - getIidStub.returns(undefined); - setupOobResources(performanceController); - - expect(apiGetInstanceSpy).not.to.be.called; - }); - - it('sets up network request collection', () => { - getIidStub.returns(MOCK_ID); - setupOobResources(performanceController); - clock.tick(1); - - expect(apiGetInstanceSpy).to.be.called; - expect(getEntriesByTypeStub).to.be.calledWith('resource'); - expect(setupObserverStub).to.be.calledWith('resource'); - }); - - it('sets up page load trace collection', () => { - getIidStub.returns(MOCK_ID); - setupOobResources(performanceController); - clock.tick(1); - - expect(apiGetInstanceSpy).to.be.called; - expect(getEntriesByTypeStub).to.be.calledWith('navigation'); - expect(getEntriesByTypeStub).to.be.calledWith('paint'); - expect(createOobTraceStub).to.be.calledWithExactly( - performanceController, - [NAVIGATION_PERFORMANCE_ENTRY], - [PAINT_PERFORMANCE_ENTRY] - ); - }); - - it('waits for first input delay if polyfill is available', () => { - getIidStub.returns(MOCK_ID); - const api = Api.getInstance(); - //@ts-ignore Assignment to read-only property. - api.onFirstInputDelay = stub(); - setupOobResources(performanceController); - clock.tick(1); - - expect(api.onFirstInputDelay).to.be.called; - expect(createOobTraceStub).not.to.be.called; - clock.tick(5000); - expect(createOobTraceStub).to.be.calledWithExactly( - performanceController, - [NAVIGATION_PERFORMANCE_ENTRY], - [PAINT_PERFORMANCE_ENTRY] - ); - }); - - it('logs first input delay if polyfill is available and callback is called', () => { - getIidStub.returns(MOCK_ID); - const api = Api.getInstance(); - const FIRST_INPUT_DELAY = 123; - // Underscore is to avoid compiler comlaining about variable being declared but not used. - type FirstInputDelayCallback = (firstInputDelay: number) => void; - let firstInputDelayCallback: FirstInputDelayCallback = (): void => {}; - //@ts-ignore Assignment to read-only property. - api.onFirstInputDelay = (cb: FirstInputDelayCallback) => { - firstInputDelayCallback = cb; - }; - setupOobResources(performanceController); - clock.tick(1); - firstInputDelayCallback(FIRST_INPUT_DELAY); - - expect(createOobTraceStub).to.be.calledWithExactly( - performanceController, - [NAVIGATION_PERFORMANCE_ENTRY], - [PAINT_PERFORMANCE_ENTRY], - FIRST_INPUT_DELAY - ); - }); - - it('sets up user timing traces', () => { - getIidStub.returns(MOCK_ID); - setupOobResources(performanceController); - clock.tick(1); - - expect(apiGetInstanceSpy).to.be.called; - expect(getEntriesByTypeStub).to.be.calledWith('measure'); - expect(setupObserverStub).to.be.calledWith('measure'); - }); - }); -}); diff --git a/packages-exp/performance-exp/src/services/oob_resources_service.ts b/packages-exp/performance-exp/src/services/oob_resources_service.ts deleted file mode 100644 index 66891e774fe..00000000000 --- a/packages-exp/performance-exp/src/services/oob_resources_service.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Api } from './api_service'; -import { Trace } from '../resources/trace'; -import { createNetworkRequestEntry } from '../resources/network_request'; -import { TRACE_MEASURE_PREFIX } from '../constants'; -import { getIid } from './iid_service'; -import { PerformanceController } from '../controllers/perf'; - -const FID_WAIT_TIME_MS = 5000; - -export function setupOobResources( - performanceController: PerformanceController -): void { - // Do not initialize unless iid is available. - if (!getIid()) { - return; - } - // The load event might not have fired yet, and that means performance navigation timing - // object has a duration of 0. The setup should run after all current tasks in js queue. - setTimeout(() => setupOobTraces(performanceController), 0); - setTimeout(() => setupNetworkRequests(performanceController), 0); - setTimeout(() => setupUserTimingTraces(performanceController), 0); -} - -function setupNetworkRequests( - performanceController: PerformanceController -): void { - const api = Api.getInstance(); - const resources = api.getEntriesByType('resource'); - for (const resource of resources) { - createNetworkRequestEntry(performanceController, resource); - } - api.setupObserver('resource', entry => - createNetworkRequestEntry(performanceController, entry) - ); -} - -function setupOobTraces(performanceController: PerformanceController): void { - const api = Api.getInstance(); - const navigationTimings = api.getEntriesByType( - 'navigation' - ) as PerformanceNavigationTiming[]; - const paintTimings = api.getEntriesByType('paint'); - // If First Input Desly polyfill is added to the page, report the fid value. - // https://github.com/GoogleChromeLabs/first-input-delay - if (api.onFirstInputDelay) { - // If the fid call back is not called for certain time, continue without it. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let timeoutId: any = setTimeout(() => { - Trace.createOobTrace( - performanceController, - navigationTimings, - paintTimings - ); - timeoutId = undefined; - }, FID_WAIT_TIME_MS); - api.onFirstInputDelay((fid: number) => { - if (timeoutId) { - clearTimeout(timeoutId); - Trace.createOobTrace( - performanceController, - navigationTimings, - paintTimings, - fid - ); - } - }); - } else { - Trace.createOobTrace( - performanceController, - navigationTimings, - paintTimings - ); - } -} - -function setupUserTimingTraces( - performanceController: PerformanceController -): void { - const api = Api.getInstance(); - // Run through the measure performance entries collected up to this point. - const measures = api.getEntriesByType('measure'); - for (const measure of measures) { - createUserTimingTrace(performanceController, measure); - } - // Setup an observer to capture the measures from this point on. - api.setupObserver('measure', entry => - createUserTimingTrace(performanceController, entry) - ); -} - -function createUserTimingTrace( - performanceController: PerformanceController, - measure: PerformanceEntry -): void { - const measureName = measure.name; - // Do not create a trace, if the user timing marks and measures are created by the sdk itself. - if ( - measureName.substring(0, TRACE_MEASURE_PREFIX.length) === - TRACE_MEASURE_PREFIX - ) { - return; - } - Trace.createUserTimingTrace(performanceController, measureName); -} diff --git a/packages-exp/performance-exp/src/services/perf_logger.test.ts b/packages-exp/performance-exp/src/services/perf_logger.test.ts deleted file mode 100644 index 2f2a70c35f4..00000000000 --- a/packages-exp/performance-exp/src/services/perf_logger.test.ts +++ /dev/null @@ -1,435 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stub, SinonStub, useFakeTimers, SinonFakeTimers } from 'sinon'; -import { Trace } from '../resources/trace'; -import * as transportService from './transport_service'; -import * as iidService from './iid_service'; -import { expect } from 'chai'; -import { Api, setupApi } from './api_service'; -import { SettingsService } from './settings_service'; -import { FirebaseApp } from '@firebase/app-exp'; -import * as initializationService from './initialization_service'; -import { SDK_VERSION } from '../constants'; -import * as attributeUtils from '../utils/attributes_utils'; -import { createNetworkRequestEntry } from '../resources/network_request'; -import '../../test/setup'; -import { mergeStrings } from '../utils/string_merger'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import { PerformanceController } from '../controllers/perf'; - -describe('Performance Monitoring > perf_logger', () => { - const IID = 'idasdfsffe'; - const PAGE_URL = 'http://mock-page.com'; - const APP_ID = '1:123:web:2er'; - const VISIBILITY_STATE = 3; - const EFFECTIVE_CONNECTION_TYPE = 2; - const SERVICE_WORKER_STATUS = 3; - const TIME_ORIGIN = 1556512199893.9033; - const TRACE_NAME = 'testTrace'; - const START_TIME = 12345; - const DURATION = 321; - // Perf event header which is constant across tests in this file. - const WEBAPP_INFO = `"application_info":{"google_app_id":"${APP_ID}",\ -"app_instance_id":"${IID}","web_app_info":{"sdk_version":"${SDK_VERSION}",\ -"page_url":"${PAGE_URL}","service_worker_status":${SERVICE_WORKER_STATUS},\ -"visibility_state":${VISIBILITY_STATE},"effective_connection_type":${EFFECTIVE_CONNECTION_TYPE}},\ -"application_process_state":0}`; - - let addToQueueStub: SinonStub< - Array<{ message: string; eventTime: number }>, - void - >; - let getIidStub: SinonStub<[], string | undefined>; - let clock: SinonFakeTimers; - - function mockTransportHandler( - serializer: (...args: any[]) => string - ): (...args: any[]) => void { - return (...args) => { - const message = serializer(...args); - addToQueueStub({ - message, - eventTime: Date.now() - }); - }; - } - - setupApi(self); - const fakeFirebaseApp = ({ - options: { appId: APP_ID } - } as unknown) as FirebaseApp; - - const fakeInstallations = ({} as unknown) as FirebaseInstallations; - const performanceController = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - - beforeEach(() => { - getIidStub = stub(iidService, 'getIid'); - addToQueueStub = stub(); - stub(transportService, 'transportHandler').callsFake(mockTransportHandler); - stub(Api.prototype, 'getUrl').returns(PAGE_URL); - stub(Api.prototype, 'getTimeOrigin').returns(TIME_ORIGIN); - stub(attributeUtils, 'getEffectiveConnectionType').returns( - EFFECTIVE_CONNECTION_TYPE - ); - stub(attributeUtils, 'getServiceWorkerStatus').returns( - SERVICE_WORKER_STATUS - ); - clock = useFakeTimers(); - }); - - describe('logTrace', () => { - it('will not drop custom events sent before initialization finishes', async () => { - getIidStub.returns(IID); - stub(attributeUtils, 'getVisibilityState').returns(VISIBILITY_STATE); - stub(initializationService, 'isPerfInitialized').returns(false); - - // Simulates logging being enabled after initialization completes. - const initializationPromise = Promise.resolve().then(() => { - SettingsService.getInstance().loggingEnabled = true; - SettingsService.getInstance().logTraceAfterSampling = true; - }); - stub(initializationService, 'getInitializationPromise').returns( - initializationPromise - ); - - const trace = new Trace(performanceController, TRACE_NAME); - trace.record(START_TIME, DURATION); - await initializationPromise.then(() => { - clock.tick(1); - }); - - expect(addToQueueStub).to.be.called; - }); - - it('creates, serializes and sends a trace to transport service', () => { - const EXPECTED_TRACE_MESSAGE = - `{` + - WEBAPP_INFO + - `,"trace_metric":{"name":"${TRACE_NAME}","is_auto":false,\ -"client_start_time_us":${START_TIME * 1000},"duration_us":${DURATION * 1000},\ -"counters":{"counter1":3},"custom_attributes":{"attr":"val"}}}`; - getIidStub.returns(IID); - stub(attributeUtils, 'getVisibilityState').returns(VISIBILITY_STATE); - stub(initializationService, 'isPerfInitialized').returns(true); - SettingsService.getInstance().loggingEnabled = true; - SettingsService.getInstance().logTraceAfterSampling = true; - const trace = new Trace(performanceController, TRACE_NAME); - trace.putAttribute('attr', 'val'); - trace.putMetric('counter1', 3); - trace.record(START_TIME, DURATION); - clock.tick(1); - - expect(addToQueueStub).to.be.called; - expect(addToQueueStub.getCall(0).args[0].message).to.be.equal( - EXPECTED_TRACE_MESSAGE - ); - }); - - it('does not log an event if cookies are disabled in the browser', () => { - stub(Api.prototype, 'requiredApisAvailable').returns(false); - stub(attributeUtils, 'getVisibilityState').returns(VISIBILITY_STATE); - stub(initializationService, 'isPerfInitialized').returns(true); - const trace = new Trace(performanceController, TRACE_NAME); - trace.record(START_TIME, DURATION); - clock.tick(1); - - expect(addToQueueStub).not.to.be.called; - }); - - it('ascertains that the max number of customMetric allowed is 32', () => { - const EXPECTED_TRACE_MESSAGE = - `{` + - WEBAPP_INFO + - `,"trace_metric":{"name":"${TRACE_NAME}","is_auto":false,\ -"client_start_time_us":${START_TIME * 1000},"duration_us":${DURATION * 1000},\ -"counters":{"counter1":1,"counter2":2,"counter3":3,"counter4":4,"counter5":5,"counter6":6,\ -"counter7":7,"counter8":8,"counter9":9,"counter10":10,"counter11":11,"counter12":12,\ -"counter13":13,"counter14":14,"counter15":15,"counter16":16,"counter17":17,"counter18":18,\ -"counter19":19,"counter20":20,"counter21":21,"counter22":22,"counter23":23,"counter24":24,\ -"counter25":25,"counter26":26,"counter27":27,"counter28":28,"counter29":29,"counter30":30,\ -"counter31":31,"counter32":32}}}`; - getIidStub.returns(IID); - stub(attributeUtils, 'getVisibilityState').returns(VISIBILITY_STATE); - stub(initializationService, 'isPerfInitialized').returns(true); - SettingsService.getInstance().loggingEnabled = true; - SettingsService.getInstance().logTraceAfterSampling = true; - const trace = new Trace(performanceController, TRACE_NAME); - for (let i = 1; i <= 32; i++) { - trace.putMetric('counter' + i, i); - } - trace.record(START_TIME, DURATION); - clock.tick(1); - - expect(addToQueueStub).to.be.called; - expect(addToQueueStub.getCall(0).args[0].message).to.be.equal( - EXPECTED_TRACE_MESSAGE - ); - }); - - it('ascertains that the max number of custom attributes allowed is 5', () => { - const EXPECTED_TRACE_MESSAGE = - `{` + - WEBAPP_INFO + - `,"trace_metric":{"name":"${TRACE_NAME}","is_auto":false,\ -"client_start_time_us":${START_TIME * 1000},"duration_us":${DURATION * 1000},\ -"custom_attributes":{"attr1":"val1","attr2":"val2","attr3":"val3","attr4":"val4","attr5":"val5"}}}`; - getIidStub.returns(IID); - stub(attributeUtils, 'getVisibilityState').returns(VISIBILITY_STATE); - stub(initializationService, 'isPerfInitialized').returns(true); - SettingsService.getInstance().loggingEnabled = true; - SettingsService.getInstance().logTraceAfterSampling = true; - const trace = new Trace(performanceController, TRACE_NAME); - for (let i = 1; i <= 5; i++) { - trace.putAttribute('attr' + i, 'val' + i); - } - trace.record(START_TIME, DURATION); - clock.tick(1); - - expect(addToQueueStub).to.be.called; - expect(addToQueueStub.getCall(0).args[0].message).to.be.equal( - EXPECTED_TRACE_MESSAGE - ); - }); - }); - - describe('logPageLoadTrace', () => { - it('creates, serializes and sends a page load trace to cc service', () => { - const flooredStartTime = Math.floor(TIME_ORIGIN * 1000); - const EXPECTED_TRACE_MESSAGE = `{"application_info":{"google_app_id":"${APP_ID}",\ -"app_instance_id":"${IID}","web_app_info":{"sdk_version":"${SDK_VERSION}",\ -"page_url":"${PAGE_URL}","service_worker_status":${SERVICE_WORKER_STATUS},\ -"visibility_state":${ - attributeUtils.VisibilityState.VISIBLE - },"effective_connection_type":${EFFECTIVE_CONNECTION_TYPE}},\ -"application_process_state":0},"trace_metric":{"name":"_wt_${PAGE_URL}","is_auto":true,\ -"client_start_time_us":${flooredStartTime},"duration_us":${DURATION * 1000},\ -"counters":{"domInteractive":10000,"domContentLoadedEventEnd":20000,"loadEventEnd":10000,\ -"_fp":40000,"_fcp":50000,"_fid":90000}}}`; - stub(initializationService, 'isPerfInitialized').returns(true); - getIidStub.returns(IID); - SettingsService.getInstance().loggingEnabled = true; - SettingsService.getInstance().logTraceAfterSampling = true; - - stub(attributeUtils, 'getVisibilityState').returns( - attributeUtils.VisibilityState.VISIBLE - ); - - const navigationTiming: PerformanceNavigationTiming = { - domComplete: 100, - domContentLoadedEventEnd: 20, - domContentLoadedEventStart: 10, - domInteractive: 10, - loadEventEnd: 10, - loadEventStart: 10, - redirectCount: 10, - type: 'navigate', - unloadEventEnd: 10, - unloadEventStart: 10, - duration: DURATION - } as PerformanceNavigationTiming; - - const navigationTimings: PerformanceNavigationTiming[] = [ - navigationTiming - ]; - - const firstPaint: PerformanceEntry = { - name: 'first-paint', - startTime: 40, - duration: 100, - entryType: 'url', - toJSON() {} - }; - - const firstContentfulPaint: PerformanceEntry = { - name: 'first-contentful-paint', - startTime: 50, - duration: 100, - entryType: 'url', - toJSON() {} - }; - - const paintTimings: PerformanceEntry[] = [ - firstPaint, - firstContentfulPaint - ]; - - Trace.createOobTrace( - performanceController, - navigationTimings, - paintTimings, - 90 - ); - clock.tick(1); - - expect(addToQueueStub).to.be.called; - expect(addToQueueStub.getCall(0).args[0].message).to.be.equal( - EXPECTED_TRACE_MESSAGE - ); - }); - }); - - describe('logNetworkRequest', () => { - it('creates, serializes and sends a network request to transport service', () => { - const RESOURCE_PERFORMANCE_ENTRY: PerformanceResourceTiming = { - connectEnd: 0, - connectStart: 0, - decodedBodySize: 0, - domainLookupEnd: 0, - domainLookupStart: 0, - duration: 39.610000094398856, - encodedBodySize: 0, - entryType: 'resource', - fetchStart: 5645.689999917522, - initiatorType: 'fetch', - name: 'https://test.com/abc', - nextHopProtocol: 'http/2+quic/43', - redirectEnd: 0, - redirectStart: 0, - requestStart: 0, - responseEnd: 5685.300000011921, - responseStart: 0, - secureConnectionStart: 0, - startTime: 5645.689999917522, - transferSize: 0, - workerStart: 0, - toJSON: () => {} - }; - const START_TIME = Math.floor( - (TIME_ORIGIN + RESOURCE_PERFORMANCE_ENTRY.startTime) * 1000 - ); - const TIME_TO_RESPONSE_COMPLETED = Math.floor( - (RESOURCE_PERFORMANCE_ENTRY.responseEnd - - RESOURCE_PERFORMANCE_ENTRY.startTime) * - 1000 - ); - const EXPECTED_NETWORK_MESSAGE = - `{` + - WEBAPP_INFO + - `,\ -"network_request_metric":{"url":"${RESOURCE_PERFORMANCE_ENTRY.name}",\ -"http_method":0,"http_response_code":200,\ -"response_payload_bytes":${RESOURCE_PERFORMANCE_ENTRY.transferSize},\ -"client_start_time_us":${START_TIME},\ -"time_to_response_completed_us":${TIME_TO_RESPONSE_COMPLETED}}}`; - stub(initializationService, 'isPerfInitialized').returns(true); - getIidStub.returns(IID); - stub(attributeUtils, 'getVisibilityState').returns(VISIBILITY_STATE); - SettingsService.getInstance().loggingEnabled = true; - SettingsService.getInstance().logNetworkAfterSampling = true; - // Calls logNetworkRequest under the hood. - createNetworkRequestEntry( - performanceController, - RESOURCE_PERFORMANCE_ENTRY - ); - clock.tick(1); - - expect(addToQueueStub).to.be.called; - expect(addToQueueStub.getCall(0).args[0].message).to.be.equal( - EXPECTED_NETWORK_MESSAGE - ); - }); - - // Performance SDK doesn't instrument requests sent from SDK itself, therefore blacklist - // requests sent to cc endpoint. - it('skips performance collection if domain is cc', () => { - const CC_NETWORK_PERFORMANCE_ENTRY: PerformanceResourceTiming = { - connectEnd: 0, - connectStart: 0, - decodedBodySize: 0, - domainLookupEnd: 0, - domainLookupStart: 0, - duration: 39.610000094398856, - encodedBodySize: 0, - entryType: 'resource', - fetchStart: 5645.689999917522, - initiatorType: 'fetch', - name: 'https://firebaselogging.googleapis.com/v0cc/log?message=a', - nextHopProtocol: 'http/2+quic/43', - redirectEnd: 0, - redirectStart: 0, - requestStart: 0, - responseEnd: 5685.300000011921, - responseStart: 0, - secureConnectionStart: 0, - startTime: 5645.689999917522, - transferSize: 0, - workerStart: 0, - toJSON: () => {} - }; - stub(initializationService, 'isPerfInitialized').returns(true); - getIidStub.returns(IID); - SettingsService.getInstance().loggingEnabled = true; - SettingsService.getInstance().logNetworkAfterSampling = true; - // Calls logNetworkRequest under the hood. - createNetworkRequestEntry( - performanceController, - CC_NETWORK_PERFORMANCE_ENTRY - ); - clock.tick(1); - - expect(addToQueueStub).not.called; - }); - - // Performance SDK doesn't instrument requests sent from SDK itself, therefore blacklist - // requests sent to fl endpoint. - it('skips performance collection if domain is fl', () => { - const FL_NETWORK_PERFORMANCE_ENTRY: PerformanceResourceTiming = { - connectEnd: 0, - connectStart: 0, - decodedBodySize: 0, - domainLookupEnd: 0, - domainLookupStart: 0, - duration: 39.610000094398856, - encodedBodySize: 0, - entryType: 'resource', - fetchStart: 5645.689999917522, - initiatorType: 'fetch', - name: mergeStrings( - 'hts/frbslgigp.ogepscmv/ieo/eaylg', - 'tp:/ieaeogn-agolai.o/1frlglgc/o' - ), - nextHopProtocol: 'http/2+quic/43', - redirectEnd: 0, - redirectStart: 0, - requestStart: 0, - responseEnd: 5685.300000011921, - responseStart: 0, - secureConnectionStart: 0, - startTime: 5645.689999917522, - transferSize: 0, - workerStart: 0, - toJSON: () => {} - }; - stub(initializationService, 'isPerfInitialized').returns(true); - getIidStub.returns(IID); - SettingsService.getInstance().loggingEnabled = true; - SettingsService.getInstance().logNetworkAfterSampling = true; - // Calls logNetworkRequest under the hood. - createNetworkRequestEntry( - performanceController, - FL_NETWORK_PERFORMANCE_ENTRY - ); - clock.tick(1); - - expect(addToQueueStub).not.called; - }); - }); -}); diff --git a/packages-exp/performance-exp/src/services/perf_logger.ts b/packages-exp/performance-exp/src/services/perf_logger.ts deleted file mode 100644 index ac506dab516..00000000000 --- a/packages-exp/performance-exp/src/services/perf_logger.ts +++ /dev/null @@ -1,250 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getIid } from './iid_service'; -import { NetworkRequest } from '../resources/network_request'; -import { Trace } from '../resources/trace'; -import { Api } from './api_service'; -import { SettingsService } from './settings_service'; -import { - getServiceWorkerStatus, - getVisibilityState, - VisibilityState, - getEffectiveConnectionType -} from '../utils/attributes_utils'; -import { - isPerfInitialized, - getInitializationPromise -} from './initialization_service'; -import { transportHandler } from './transport_service'; -import { SDK_VERSION } from '../constants'; -import { FirebaseApp } from '@firebase/app-exp'; -import { getAppId } from '../utils/app_utils'; - -const enum ResourceType { - NetworkRequest, - Trace -} - -/* eslint-disable camelcase */ -interface ApplicationInfo { - google_app_id: string; - app_instance_id?: string; - web_app_info: WebAppInfo; - application_process_state: number; -} - -interface WebAppInfo { - sdk_version: string; - page_url: string; - service_worker_status: number; - visibility_state: number; - effective_connection_type: number; -} - -interface PerfNetworkLog { - application_info: ApplicationInfo; - network_request_metric: NetworkRequestMetric; -} - -interface PerfTraceLog { - application_info: ApplicationInfo; - trace_metric: TraceMetric; -} - -interface NetworkRequestMetric { - url: string; - http_method: number; - http_response_code: number; - response_payload_bytes?: number; - client_start_time_us?: number; - time_to_response_initiated_us?: number; - time_to_response_completed_us?: number; -} - -interface TraceMetric { - name: string; - is_auto: boolean; - client_start_time_us: number; - duration_us: number; - counters?: { [key: string]: number }; - custom_attributes?: { [key: string]: string }; -} - -/* eslint-enble camelcase */ - -let logger: ( - resource: NetworkRequest | Trace, - resourceType: ResourceType -) => void | undefined; -// This method is not called before initialization. -function sendLog( - resource: NetworkRequest | Trace, - resourceType: ResourceType -): void { - if (!logger) { - logger = transportHandler(serializer); - } - logger(resource, resourceType); -} - -export function logTrace(trace: Trace): void { - const settingsService = SettingsService.getInstance(); - // Do not log if trace is auto generated and instrumentation is disabled. - if (!settingsService.instrumentationEnabled && trace.isAuto) { - return; - } - // Do not log if trace is custom and data collection is disabled. - if (!settingsService.dataCollectionEnabled && !trace.isAuto) { - return; - } - // Do not log if required apis are not available. - if (!Api.getInstance().requiredApisAvailable()) { - return; - } - - // Only log the page load auto traces if page is visible. - if (trace.isAuto && getVisibilityState() !== VisibilityState.VISIBLE) { - return; - } - - if (isPerfInitialized()) { - sendTraceLog(trace); - } else { - // Custom traces can be used before the initialization but logging - // should wait until after. - getInitializationPromise(trace.performanceController).then( - () => sendTraceLog(trace), - () => sendTraceLog(trace) - ); - } -} - -function sendTraceLog(trace: Trace): void { - if (!getIid()) { - return; - } - - const settingsService = SettingsService.getInstance(); - if ( - !settingsService.loggingEnabled || - !settingsService.logTraceAfterSampling - ) { - return; - } - - setTimeout(() => sendLog(trace, ResourceType.Trace), 0); -} - -export function logNetworkRequest(networkRequest: NetworkRequest): void { - const settingsService = SettingsService.getInstance(); - // Do not log network requests if instrumentation is disabled. - if (!settingsService.instrumentationEnabled) { - return; - } - - // Do not log the js sdk's call to transport service domain to avoid unnecessary cycle. - // Need to blacklist both old and new endpoints to avoid migration gap. - const networkRequestUrl = networkRequest.url; - - // Blacklist old log endpoint and new transport endpoint. - // Because Performance SDK doesn't instrument requests sent from SDK itself. - const logEndpointUrl = settingsService.logEndPointUrl.split('?')[0]; - const flEndpointUrl = settingsService.flTransportEndpointUrl.split('?')[0]; - if ( - networkRequestUrl === logEndpointUrl || - networkRequestUrl === flEndpointUrl - ) { - return; - } - - if ( - !settingsService.loggingEnabled || - !settingsService.logNetworkAfterSampling - ) { - return; - } - - setTimeout(() => sendLog(networkRequest, ResourceType.NetworkRequest), 0); -} - -function serializer( - resource: NetworkRequest | Trace, - resourceType: ResourceType -): string { - if (resourceType === ResourceType.NetworkRequest) { - return serializeNetworkRequest(resource as NetworkRequest); - } - return serializeTrace(resource as Trace); -} - -function serializeNetworkRequest(networkRequest: NetworkRequest): string { - const networkRequestMetric: NetworkRequestMetric = { - url: networkRequest.url, - http_method: networkRequest.httpMethod || 0, - http_response_code: 200, - response_payload_bytes: networkRequest.responsePayloadBytes, - client_start_time_us: networkRequest.startTimeUs, - time_to_response_initiated_us: networkRequest.timeToResponseInitiatedUs, - time_to_response_completed_us: networkRequest.timeToResponseCompletedUs - }; - const perfMetric: PerfNetworkLog = { - application_info: getApplicationInfo( - networkRequest.performanceController.app - ), - network_request_metric: networkRequestMetric - }; - return JSON.stringify(perfMetric); -} - -function serializeTrace(trace: Trace): string { - const traceMetric: TraceMetric = { - name: trace.name, - is_auto: trace.isAuto, - client_start_time_us: trace.startTimeUs, - duration_us: trace.durationUs - }; - - if (Object.keys(trace.counters).length !== 0) { - traceMetric.counters = trace.counters; - } - const customAttributes = trace.getAttributes(); - if (Object.keys(customAttributes).length !== 0) { - traceMetric.custom_attributes = customAttributes; - } - - const perfMetric: PerfTraceLog = { - application_info: getApplicationInfo(trace.performanceController.app), - trace_metric: traceMetric - }; - return JSON.stringify(perfMetric); -} - -function getApplicationInfo(firebaseApp: FirebaseApp): ApplicationInfo { - return { - google_app_id: getAppId(firebaseApp), - app_instance_id: getIid(), - web_app_info: { - sdk_version: SDK_VERSION, - page_url: Api.getInstance().getUrl(), - service_worker_status: getServiceWorkerStatus(), - visibility_state: getVisibilityState(), - effective_connection_type: getEffectiveConnectionType() - }, - application_process_state: 0 - }; -} diff --git a/packages-exp/performance-exp/src/services/remote_config_service.test.ts b/packages-exp/performance-exp/src/services/remote_config_service.test.ts deleted file mode 100644 index 9baf2d3a8a8..00000000000 --- a/packages-exp/performance-exp/src/services/remote_config_service.test.ts +++ /dev/null @@ -1,284 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stub, useFakeTimers, SinonFakeTimers, SinonStub } from 'sinon'; -import { expect } from 'chai'; -import { SettingsService } from './settings_service'; -import { CONFIG_EXPIRY_LOCAL_STORAGE_KEY } from '../constants'; -import { setupApi, Api } from './api_service'; -import * as iidService from './iid_service'; -import { getConfig } from './remote_config_service'; -import { FirebaseApp } from '@firebase/app-exp'; -import '../../test/setup'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import { PerformanceController } from '../controllers/perf'; - -describe('Performance Monitoring > remote_config_service', () => { - const IID = 'asd123'; - const AUTH_TOKEN = 'auth_token'; - const LOG_URL = 'https://firebaselogging.test.com'; - const TRANSPORT_KEY = 'pseudo-transport-key'; - const LOG_SOURCE = 2; - const NETWORK_SAMPLIG_RATE = 0.25; - const TRACE_SAMPLING_RATE = 0.5; - const GLOBAL_CLOCK_NOW = 1556524895326; - const STRINGIFIED_CONFIG = `{"entries":{"fpr_enabled":"true",\ - "fpr_log_endpoint_url":"https://firebaselogging.test.com",\ - "fpr_log_transport_key":"pseudo-transport-key",\ - "fpr_log_source":"2","fpr_vc_network_request_sampling_rate":"0.250000",\ - "fpr_vc_session_sampling_rate":"0.250000","fpr_vc_trace_sampling_rate":"0.500000"},\ - "state":"UPDATE"}`; - const PROJECT_ID = 'project1'; - const APP_ID = '1:23r:web:fewq'; - const API_KEY = 'asdfghjk'; - const NOT_VALID_CONFIG = 'not a valid config and should not be used'; - - let clock: SinonFakeTimers; - - setupApi(self); - const ApiInstance = Api.getInstance(); - - const fakeFirebaseApp = ({ - options: { projectId: PROJECT_ID, appId: APP_ID, apiKey: API_KEY } - } as unknown) as FirebaseApp; - - const fakeInstallations = ({} as unknown) as FirebaseInstallations; - const performanceController = new PerformanceController( - fakeFirebaseApp, - fakeInstallations - ); - - function storageGetItemFakeFactory( - expiry: string, - config: string - ): (key: string) => string { - return (key: string) => { - if (key === CONFIG_EXPIRY_LOCAL_STORAGE_KEY) { - return expiry; - } - return config; - }; - } - - function resetSettingsService(): void { - const settingsService = SettingsService.getInstance(); - settingsService.logSource = 462; - settingsService.loggingEnabled = false; - settingsService.networkRequestsSamplingRate = 1; - settingsService.tracesSamplingRate = 1; - } - - // parameterized beforeEach. Should be called at beginning of each test. - function setup( - storageConfig: { expiry: string; config: string }, - fetchConfig?: { reject: boolean; value?: Response } - ): { - storageGetItemStub: SinonStub<[string], string | null>; - fetchStub: SinonStub<[RequestInfo, RequestInit?], Promise>; - } { - const fetchStub = stub(self, 'fetch'); - - if (fetchConfig) { - fetchConfig.reject - ? fetchStub.rejects() - : fetchStub.resolves(fetchConfig.value); - } - - stub(iidService, 'getAuthTokenPromise').returns( - Promise.resolve(AUTH_TOKEN) - ); - - clock = useFakeTimers(GLOBAL_CLOCK_NOW); - - // we need to stub the entire localStorage, because storage can't be stubbed in Firefox and IE. - // stubbing on self(window) seems to only work the first time (at least in Firefox), the subsequent - // tests will have the same stub. stub.reset() in afterEach doesn't help either. As a result, we stub on ApiInstance. - // https://github.com/sinonjs/sinon/issues/662 - const storageStub = stub(ApiInstance, 'localStorage'); - const getItemStub: SinonStub<[string], string | null> = stub(); - - storageStub.value({ - getItem: getItemStub.callsFake( - storageGetItemFakeFactory(storageConfig.expiry, storageConfig.config) - ), - setItem: () => {} - }); - - return { storageGetItemStub: getItemStub, fetchStub }; - } - - afterEach(() => { - resetSettingsService(); - clock.restore(); - }); - - describe('getConfig', () => { - it('gets the config from the local storage if available and valid', async () => { - // After global clock. Config not expired. - const EXPIRY_LOCAL_STORAGE_VALUE = '1556524895330'; - const { storageGetItemStub: getItemStub } = setup({ - expiry: EXPIRY_LOCAL_STORAGE_VALUE, - config: STRINGIFIED_CONFIG - }); - - await getConfig(performanceController, IID); - - expect(getItemStub).to.be.called; - expect(SettingsService.getInstance().loggingEnabled).to.be.true; - expect(SettingsService.getInstance().logEndPointUrl).to.equal(LOG_URL); - expect(SettingsService.getInstance().transportKey).to.equal( - TRANSPORT_KEY - ); - expect(SettingsService.getInstance().logSource).to.equal(LOG_SOURCE); - expect( - SettingsService.getInstance().networkRequestsSamplingRate - ).to.equal(NETWORK_SAMPLIG_RATE); - expect(SettingsService.getInstance().tracesSamplingRate).to.equal( - TRACE_SAMPLING_RATE - ); - }); - - it('does not call remote config if a valid config is in local storage', async () => { - // After global clock. Config not expired. - const EXPIRY_LOCAL_STORAGE_VALUE = '1556524895330'; - - const { fetchStub } = setup({ - expiry: EXPIRY_LOCAL_STORAGE_VALUE, - config: STRINGIFIED_CONFIG - }); - - await getConfig(performanceController, IID); - - expect(fetchStub).not.to.be.called; - }); - - it('gets the config from RC if local version is not valid', async () => { - // Expired local config. - const EXPIRY_LOCAL_STORAGE_VALUE = '1556524895320'; - - const { storageGetItemStub: getItemStub } = setup( - { expiry: EXPIRY_LOCAL_STORAGE_VALUE, config: STRINGIFIED_CONFIG }, - { reject: false, value: new Response(STRINGIFIED_CONFIG) } - ); - - await getConfig(performanceController, IID); - - expect(getItemStub).to.be.calledOnce; - expect(SettingsService.getInstance().loggingEnabled).to.be.true; - expect(SettingsService.getInstance().logEndPointUrl).to.equal(LOG_URL); - expect(SettingsService.getInstance().transportKey).to.equal( - TRANSPORT_KEY - ); - expect(SettingsService.getInstance().logSource).to.equal(LOG_SOURCE); - expect( - SettingsService.getInstance().networkRequestsSamplingRate - ).to.equal(NETWORK_SAMPLIG_RATE); - expect(SettingsService.getInstance().tracesSamplingRate).to.equal( - TRACE_SAMPLING_RATE - ); - }); - - it('does not change the default config if call to RC fails', async () => { - // Expired local config. - const EXPIRY_LOCAL_STORAGE_VALUE = '1556524895320'; - - setup( - { - expiry: EXPIRY_LOCAL_STORAGE_VALUE, - config: NOT_VALID_CONFIG - }, - { reject: true } - ); - - await getConfig(performanceController, IID); - - expect(SettingsService.getInstance().loggingEnabled).to.equal(false); - }); - - it('uses secondary configs if the response does not have all the fields', async () => { - // Expired local config. - const EXPIRY_LOCAL_STORAGE_VALUE = '1556524895320'; - const STRINGIFIED_PARTIAL_CONFIG = `{"entries":{\ - "fpr_vc_network_request_sampling_rate":"0.250000",\ - "fpr_vc_session_sampling_rate":"0.250000","fpr_vc_trace_sampling_rate":"0.500000"},\ - "state":"UPDATE"}`; - - setup( - { - expiry: EXPIRY_LOCAL_STORAGE_VALUE, - config: NOT_VALID_CONFIG - }, - { reject: false, value: new Response(STRINGIFIED_PARTIAL_CONFIG) } - ); - - await getConfig(performanceController, IID); - - expect(SettingsService.getInstance().loggingEnabled).to.be.true; - }); - - it('uses secondary configs if the response does not have any fields', async () => { - // Expired local config. - const EXPIRY_LOCAL_STORAGE_VALUE = '1556524895320'; - const STRINGIFIED_PARTIAL_CONFIG = '{"state":"NO_TEMPLATE"}'; - - setup( - { - expiry: EXPIRY_LOCAL_STORAGE_VALUE, - config: NOT_VALID_CONFIG - }, - { reject: false, value: new Response(STRINGIFIED_PARTIAL_CONFIG) } - ); - await getConfig(performanceController, IID); - - expect(SettingsService.getInstance().loggingEnabled).to.be.true; - }); - - it('gets the config from RC even with deprecated transport flag', async () => { - // Expired local config. - const EXPIRY_LOCAL_STORAGE_VALUE = '1556524895320'; - const STRINGIFIED_CUSTOM_CONFIG = `{"entries":{\ - "fpr_vc_network_request_sampling_rate":"0.250000",\ - "fpr_log_transport_web_percent":"100.0",\ - "fpr_vc_session_sampling_rate":"0.250000","fpr_vc_trace_sampling_rate":"0.500000"},\ - "state":"UPDATE"}`; - - const { storageGetItemStub: getItemStub } = setup( - { - expiry: EXPIRY_LOCAL_STORAGE_VALUE, - config: STRINGIFIED_CUSTOM_CONFIG - }, - { reject: false, value: new Response(STRINGIFIED_CONFIG) } - ); - - await getConfig(performanceController, IID); - - expect(getItemStub).to.be.calledOnce; - expect(SettingsService.getInstance().loggingEnabled).to.be.true; - expect(SettingsService.getInstance().logEndPointUrl).to.equal(LOG_URL); - expect(SettingsService.getInstance().transportKey).to.equal( - TRANSPORT_KEY - ); - expect(SettingsService.getInstance().logSource).to.equal(LOG_SOURCE); - expect( - SettingsService.getInstance().networkRequestsSamplingRate - ).to.equal(NETWORK_SAMPLIG_RATE); - expect(SettingsService.getInstance().tracesSamplingRate).to.equal( - TRACE_SAMPLING_RATE - ); - }); - }); -}); diff --git a/packages-exp/performance-exp/src/services/remote_config_service.ts b/packages-exp/performance-exp/src/services/remote_config_service.ts deleted file mode 100644 index 558e995e9d6..00000000000 --- a/packages-exp/performance-exp/src/services/remote_config_service.ts +++ /dev/null @@ -1,240 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - CONFIG_EXPIRY_LOCAL_STORAGE_KEY, - CONFIG_LOCAL_STORAGE_KEY, - SDK_VERSION -} from '../constants'; -import { consoleLogger } from '../utils/console_logger'; -import { ERROR_FACTORY, ErrorCode } from '../utils/errors'; - -import { Api } from './api_service'; -import { getAuthTokenPromise } from './iid_service'; -import { SettingsService } from './settings_service'; -import { PerformanceController } from '../controllers/perf'; -import { getProjectId, getApiKey, getAppId } from '../utils/app_utils'; - -const REMOTE_CONFIG_SDK_VERSION = '0.0.1'; - -interface SecondaryConfig { - loggingEnabled?: boolean; - logSource?: number; - logEndPointUrl?: string; - transportKey?: string; - tracesSamplingRate?: number; - networkRequestsSamplingRate?: number; -} - -// These values will be used if the remote config object is successfully -// retrieved, but the template does not have these fields. -const DEFAULT_CONFIGS: SecondaryConfig = { - loggingEnabled: true -}; - -/* eslint-disable camelcase */ -interface RemoteConfigTemplate { - fpr_enabled?: string; - fpr_log_source?: string; - fpr_log_endpoint_url?: string; - fpr_log_transport_key?: string; - fpr_log_transport_web_percent?: string; - fpr_vc_network_request_sampling_rate?: string; - fpr_vc_trace_sampling_rate?: string; - fpr_vc_session_sampling_rate?: string; -} -/* eslint-enable camelcase */ - -interface RemoteConfigResponse { - entries?: RemoteConfigTemplate; - state?: string; -} - -const FIS_AUTH_PREFIX = 'FIREBASE_INSTALLATIONS_AUTH'; - -export function getConfig( - performanceController: PerformanceController, - iid: string -): Promise { - const config = getStoredConfig(); - if (config) { - processConfig(config); - return Promise.resolve(); - } - - return getRemoteConfig(performanceController, iid) - .then(processConfig) - .then( - config => storeConfig(config), - /** Do nothing for error, use defaults set in settings service. */ - () => {} - ); -} - -function getStoredConfig(): RemoteConfigResponse | undefined { - const localStorage = Api.getInstance().localStorage; - if (!localStorage) { - return; - } - const expiryString = localStorage.getItem(CONFIG_EXPIRY_LOCAL_STORAGE_KEY); - if (!expiryString || !configValid(expiryString)) { - return; - } - - const configStringified = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY); - if (!configStringified) { - return; - } - try { - const configResponse: RemoteConfigResponse = JSON.parse(configStringified); - return configResponse; - } catch { - return; - } -} - -function storeConfig(config: RemoteConfigResponse | undefined): void { - const localStorage = Api.getInstance().localStorage; - if (!config || !localStorage) { - return; - } - - localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config)); - localStorage.setItem( - CONFIG_EXPIRY_LOCAL_STORAGE_KEY, - String( - Date.now() + - SettingsService.getInstance().configTimeToLive * 60 * 60 * 1000 - ) - ); -} - -const COULD_NOT_GET_CONFIG_MSG = - 'Could not fetch config, will use default configs'; - -function getRemoteConfig( - performanceController: PerformanceController, - iid: string -): Promise { - // Perf needs auth token only to retrieve remote config. - return getAuthTokenPromise(performanceController.installations) - .then(authToken => { - const projectId = getProjectId(performanceController.app); - const apiKey = getApiKey(performanceController.app); - const configEndPoint = `https://firebaseremoteconfig.googleapis.com/v1/projects/${projectId}/namespaces/fireperf:fetch?key=${apiKey}`; - const request = new Request(configEndPoint, { - method: 'POST', - headers: { Authorization: `${FIS_AUTH_PREFIX} ${authToken}` }, - /* eslint-disable camelcase */ - body: JSON.stringify({ - app_instance_id: iid, - app_instance_id_token: authToken, - app_id: getAppId(performanceController.app), - app_version: SDK_VERSION, - sdk_version: REMOTE_CONFIG_SDK_VERSION - }) - /* eslint-enable camelcase */ - }); - return fetch(request).then(response => { - if (response.ok) { - return response.json() as RemoteConfigResponse; - } - // In case response is not ok. This will be caught by catch. - throw ERROR_FACTORY.create(ErrorCode.RC_NOT_OK); - }); - }) - .catch(() => { - consoleLogger.info(COULD_NOT_GET_CONFIG_MSG); - return undefined; - }); -} - -/** - * Processes config coming either from calling RC or from local storage. - * This method only runs if call is successful or config in storage - * is valid. - */ -function processConfig( - config?: RemoteConfigResponse -): RemoteConfigResponse | undefined { - if (!config) { - return config; - } - const settingsServiceInstance = SettingsService.getInstance(); - const entries = config.entries || {}; - if (entries.fpr_enabled !== undefined) { - // TODO: Change the assignment of loggingEnabled once the received type is - // known. - settingsServiceInstance.loggingEnabled = - String(entries.fpr_enabled) === 'true'; - } else if (DEFAULT_CONFIGS.loggingEnabled !== undefined) { - // Config retrieved successfully, but there is no fpr_enabled in template. - // Use secondary configs value. - settingsServiceInstance.loggingEnabled = DEFAULT_CONFIGS.loggingEnabled; - } - if (entries.fpr_log_source) { - settingsServiceInstance.logSource = Number(entries.fpr_log_source); - } else if (DEFAULT_CONFIGS.logSource) { - settingsServiceInstance.logSource = DEFAULT_CONFIGS.logSource; - } - - if (entries.fpr_log_endpoint_url) { - settingsServiceInstance.logEndPointUrl = entries.fpr_log_endpoint_url; - } else if (DEFAULT_CONFIGS.logEndPointUrl) { - settingsServiceInstance.logEndPointUrl = DEFAULT_CONFIGS.logEndPointUrl; - } - - // Key from Remote Config has to be non-empty string, otherwsie use local value. - if (entries.fpr_log_transport_key) { - settingsServiceInstance.transportKey = entries.fpr_log_transport_key; - } else if (DEFAULT_CONFIGS.transportKey) { - settingsServiceInstance.transportKey = DEFAULT_CONFIGS.transportKey; - } - - if (entries.fpr_vc_network_request_sampling_rate !== undefined) { - settingsServiceInstance.networkRequestsSamplingRate = Number( - entries.fpr_vc_network_request_sampling_rate - ); - } else if (DEFAULT_CONFIGS.networkRequestsSamplingRate !== undefined) { - settingsServiceInstance.networkRequestsSamplingRate = - DEFAULT_CONFIGS.networkRequestsSamplingRate; - } - if (entries.fpr_vc_trace_sampling_rate !== undefined) { - settingsServiceInstance.tracesSamplingRate = Number( - entries.fpr_vc_trace_sampling_rate - ); - } else if (DEFAULT_CONFIGS.tracesSamplingRate !== undefined) { - settingsServiceInstance.tracesSamplingRate = - DEFAULT_CONFIGS.tracesSamplingRate; - } - // Set the per session trace and network logging flags. - settingsServiceInstance.logTraceAfterSampling = shouldLogAfterSampling( - settingsServiceInstance.tracesSamplingRate - ); - settingsServiceInstance.logNetworkAfterSampling = shouldLogAfterSampling( - settingsServiceInstance.networkRequestsSamplingRate - ); - return config; -} - -function configValid(expiry: string): boolean { - return Number(expiry) > Date.now(); -} - -function shouldLogAfterSampling(samplingRate: number): boolean { - return Math.random() <= samplingRate; -} diff --git a/packages-exp/performance-exp/src/services/settings_service.ts b/packages-exp/performance-exp/src/services/settings_service.ts deleted file mode 100644 index 83e08bd53d5..00000000000 --- a/packages-exp/performance-exp/src/services/settings_service.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { mergeStrings } from '../utils/string_merger'; - -let settingsServiceInstance: SettingsService | undefined; - -export class SettingsService { - // The variable which controls logging of automatic traces and HTTP/S network monitoring. - instrumentationEnabled = true; - - // The variable which controls logging of custom traces. - dataCollectionEnabled = true; - - // Configuration flags set through remote config. - loggingEnabled = false; - // Sampling rate between 0 and 1. - tracesSamplingRate = 1; - networkRequestsSamplingRate = 1; - - // Address of logging service. - logEndPointUrl = - 'https://firebaselogging.googleapis.com/v0cc/log?format=json_proto'; - // Performance event transport endpoint URL which should be compatible with proto3. - // New Address for transport service, not configurable via Remote Config. - flTransportEndpointUrl = mergeStrings( - 'hts/frbslgigp.ogepscmv/ieo/eaylg', - 'tp:/ieaeogn-agolai.o/1frlglgc/o' - ); - - transportKey = mergeStrings('AzSC8r6ReiGqFMyfvgow', 'Iayx0u-XT3vksVM-pIV'); - - // Source type for performance event logs. - logSource = 462; - - // Flags which control per session logging of traces and network requests. - logTraceAfterSampling = false; - logNetworkAfterSampling = false; - - // TTL of config retrieved from remote config in hours. - configTimeToLive = 12; - - getFlTransportFullUrl(): string { - return this.flTransportEndpointUrl.concat('?key=', this.transportKey); - } - - static getInstance(): SettingsService { - if (settingsServiceInstance === undefined) { - settingsServiceInstance = new SettingsService(); - } - return settingsServiceInstance; - } -} diff --git a/packages-exp/performance-exp/src/services/transport_service.test.ts b/packages-exp/performance-exp/src/services/transport_service.test.ts deleted file mode 100644 index 43986e00817..00000000000 --- a/packages-exp/performance-exp/src/services/transport_service.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stub, useFakeTimers, SinonStub, SinonFakeTimers, match } from 'sinon'; -import { use, expect } from 'chai'; -import * as sinonChai from 'sinon-chai'; -import { - transportHandler, - setupTransportService, - resetTransportService -} from './transport_service'; -import { SettingsService } from './settings_service'; - -use(sinonChai); - -describe('Firebase Performance > transport_service', () => { - let fetchStub: SinonStub<[RequestInfo, RequestInit?], Promise>; - const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000; - const DEFAULT_SEND_INTERVAL_MS = 10 * 1000; - const MAX_EVENT_COUNT_PER_REQUEST = 1000; - const TRANSPORT_DELAY_INTERVAL = 10000; - // Starts date at timestamp 1 instead of 0, otherwise it causes validation errors. - let clock: SinonFakeTimers; - const testTransportHandler = transportHandler((...args) => { - return args[0]; - }); - - beforeEach(() => { - fetchStub = stub(window, 'fetch'); - clock = useFakeTimers(1); - setupTransportService(); - }); - - afterEach(() => { - fetchStub.restore(); - clock.restore(); - resetTransportService(); - }); - - it('throws an error when logging an empty message', () => { - expect(() => { - testTransportHandler(''); - }).to.throw; - }); - - it('does not attempt to log an event after INITIAL_SEND_TIME_DELAY_MS if queue is empty', () => { - fetchStub.resolves( - new Response('', { - status: 200, - headers: { 'Content-type': 'application/json' } - }) - ); - - clock.tick(INITIAL_SEND_TIME_DELAY_MS); - expect(fetchStub).to.not.have.been.called; - }); - - it('attempts to log an event after DEFAULT_SEND_INTERVAL_MS if queue not empty', async () => { - fetchStub.resolves( - new Response('', { - status: 200, - headers: { 'Content-type': 'application/json' } - }) - ); - - clock.tick(INITIAL_SEND_TIME_DELAY_MS); - testTransportHandler('someEvent'); - clock.tick(DEFAULT_SEND_INTERVAL_MS); - expect(fetchStub).to.have.been.calledOnce; - }); - - it('successful send a meesage to transport', () => { - const setting = SettingsService.getInstance(); - const flTransportFullUrl = - setting.flTransportEndpointUrl + '?key=' + setting.transportKey; - fetchStub.withArgs(flTransportFullUrl, match.any).resolves( - // DELETE_REQUEST means event dispatch is successful. - generateSuccessResponse() - ); - - testTransportHandler('event1'); - clock.tick(INITIAL_SEND_TIME_DELAY_MS); - expect(fetchStub).to.have.been.calledOnce; - }); - - it('sends up to the maximum event limit in one request', async () => { - // Arrange - const setting = SettingsService.getInstance(); - const flTransportFullUrl = - setting.flTransportEndpointUrl + '?key=' + setting.transportKey; - - // Returns successful response from fl for logRequests. - const response = generateSuccessResponse(); - stub(response, 'json').resolves(JSON.parse(generateSuccessResponseBody())); - fetchStub.resolves(response); - - // Act - // Generate 1020 events, which should be dispatched in two batches (1000 events and 20 events). - for (let i = 0; i < 1020; i++) { - testTransportHandler('event' + i); - } - // Wait for first and second event dispatch to happen. - clock.tick(INITIAL_SEND_TIME_DELAY_MS); - // This is to resolve the floating promise chain in transport service. - await Promise.resolve().then().then().then(); - clock.tick(DEFAULT_SEND_INTERVAL_MS); - - // Assert - // Expects the first logRequest which contains first 1000 events. - const firstLogRequest = generateLogRequest('5501'); - for (let i = 0; i < MAX_EVENT_COUNT_PER_REQUEST; i++) { - firstLogRequest['log_event'].push({ - 'source_extension_json_proto3': 'event' + i, - 'event_time_ms': '1' - }); - } - expect(fetchStub).which.to.have.been.calledWith(flTransportFullUrl, { - method: 'POST', - body: JSON.stringify(firstLogRequest) - }); - // Expects the second logRequest which contains remaining 20 events; - const secondLogRequest = generateLogRequest('15501'); - for (let i = 0; i < 20; i++) { - secondLogRequest['log_event'].push({ - 'source_extension_json_proto3': - 'event' + (MAX_EVENT_COUNT_PER_REQUEST + i), - 'event_time_ms': '1' - }); - } - expect(fetchStub).calledWith(flTransportFullUrl, { - method: 'POST', - body: JSON.stringify(secondLogRequest) - }); - }); - - function generateLogRequest(requestTimeMs: string): any { - return { - 'request_time_ms': requestTimeMs, - 'client_info': { - 'client_type': 1, - 'js_client_info': {} - }, - 'log_source': 462, - 'log_event': [] as any - }; - } - - function generateSuccessResponse(): Response { - return new Response(generateSuccessResponseBody(), { - status: 200, - headers: { 'Content-type': 'application/json' } - }); - } - - function generateSuccessResponseBody(): string { - return ( - '{\ - "nextRequestWaitMillis": "' + - TRANSPORT_DELAY_INTERVAL + - '",\ - "logResponseDetails": [\ - {\ - "responseAction": "DELETE_REQUEST"\ - }\ - ]\ - }' - ); - } -}); diff --git a/packages-exp/performance-exp/src/services/transport_service.ts b/packages-exp/performance-exp/src/services/transport_service.ts deleted file mode 100644 index 87f8efab773..00000000000 --- a/packages-exp/performance-exp/src/services/transport_service.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { SettingsService } from './settings_service'; -import { ERROR_FACTORY, ErrorCode } from '../utils/errors'; -import { consoleLogger } from '../utils/console_logger'; - -const DEFAULT_SEND_INTERVAL_MS = 10 * 1000; -const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000; -// If end point does not work, the call will be tried for these many times. -const DEFAULT_REMAINING_TRIES = 3; -const MAX_EVENT_COUNT_PER_REQUEST = 1000; -let remainingTries = DEFAULT_REMAINING_TRIES; - -interface LogResponseDetails { - responseAction?: string; -} - -interface BatchEvent { - message: string; - eventTime: number; -} - -/* eslint-disable camelcase */ -// CC/Fl accepted log format. -interface TransportBatchLogFormat { - request_time_ms: string; - client_info: ClientInfo; - log_source: number; - log_event: Log[]; -} - -interface ClientInfo { - client_type: number; - js_client_info: {}; -} - -interface Log { - source_extension_json_proto3: string; - event_time_ms: string; -} -/* eslint-enable camelcase */ - -let queue: BatchEvent[] = []; - -let isTransportSetup: boolean = false; - -export function setupTransportService(): void { - if (!isTransportSetup) { - processQueue(INITIAL_SEND_TIME_DELAY_MS); - isTransportSetup = true; - } -} - -/** - * Utilized by testing to clean up message queue and un-initialize transport service. - */ -export function resetTransportService(): void { - isTransportSetup = false; - queue = []; -} - -function processQueue(timeOffset: number): void { - setTimeout(() => { - // If there is no remainingTries left, stop retrying. - if (remainingTries === 0) { - return; - } - - // If there are no events to process, wait for DEFAULT_SEND_INTERVAL_MS and try again. - if (!queue.length) { - return processQueue(DEFAULT_SEND_INTERVAL_MS); - } - - dispatchQueueEvents(); - }, timeOffset); -} - -function dispatchQueueEvents(): void { - // Extract events up to the maximum cap of single logRequest from top of "official queue". - // The staged events will be used for current logRequest attempt, remaining events will be kept - // for next attempt. - const staged = queue.splice(0, MAX_EVENT_COUNT_PER_REQUEST); - - /* eslint-disable camelcase */ - // We will pass the JSON serialized event to the backend. - const log_event: Log[] = staged.map(evt => ({ - source_extension_json_proto3: evt.message, - event_time_ms: String(evt.eventTime) - })); - - const data: TransportBatchLogFormat = { - request_time_ms: String(Date.now()), - client_info: { - client_type: 1, // 1 is JS - js_client_info: {} - }, - log_source: SettingsService.getInstance().logSource, - log_event - }; - /* eslint-enable camelcase */ - - sendEventsToFl(data, staged).catch(() => { - // If the request fails for some reason, add the events that were attempted - // back to the primary queue to retry later. - queue = [...staged, ...queue]; - remainingTries--; - consoleLogger.info(`Tries left: ${remainingTries}.`); - processQueue(DEFAULT_SEND_INTERVAL_MS); - }); -} - -function sendEventsToFl( - data: TransportBatchLogFormat, - staged: BatchEvent[] -): Promise { - return postToFlEndpoint(data) - .then(res => { - if (!res.ok) { - consoleLogger.info('Call to Firebase backend failed.'); - } - return res.json(); - }) - .then(res => { - // Find the next call wait time from the response. - const transportWait = Number(res.nextRequestWaitMillis); - let requestOffset = DEFAULT_SEND_INTERVAL_MS; - if (!isNaN(transportWait)) { - requestOffset = Math.max(transportWait, requestOffset); - } - - // Delete request if response include RESPONSE_ACTION_UNKNOWN or DELETE_REQUEST action. - // Otherwise, retry request using normal scheduling if response include RETRY_REQUEST_LATER. - const logResponseDetails: LogResponseDetails[] = res.logResponseDetails; - if ( - Array.isArray(logResponseDetails) && - logResponseDetails.length > 0 && - logResponseDetails[0].responseAction === 'RETRY_REQUEST_LATER' - ) { - queue = [...staged, ...queue]; - consoleLogger.info(`Retry transport request later.`); - } - - remainingTries = DEFAULT_REMAINING_TRIES; - // Schedule the next process. - processQueue(requestOffset); - }); -} - -function postToFlEndpoint(data: TransportBatchLogFormat): Promise { - const flTransportFullUrl = SettingsService.getInstance().getFlTransportFullUrl(); - return fetch(flTransportFullUrl, { - method: 'POST', - body: JSON.stringify(data) - }); -} - -function addToQueue(evt: BatchEvent): void { - if (!evt.eventTime || !evt.message) { - throw ERROR_FACTORY.create(ErrorCode.INVALID_CC_LOG); - } - // Add the new event to the queue. - queue = [...queue, evt]; -} - -/** Log handler for cc service to send the performance logs to the server. */ -export function transportHandler( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - serializer: (...args: any[]) => string -): (...args: unknown[]) => void { - return (...args) => { - const message = serializer(...args); - addToQueue({ - message, - eventTime: Date.now() - }); - }; -} diff --git a/packages-exp/performance-exp/src/utils/attribute_utils.test.ts b/packages-exp/performance-exp/src/utils/attribute_utils.test.ts deleted file mode 100644 index bda20b3e165..00000000000 --- a/packages-exp/performance-exp/src/utils/attribute_utils.test.ts +++ /dev/null @@ -1,215 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF unknown KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { restore, stub } from 'sinon'; -import { expect } from 'chai'; -import { Api } from '../services/api_service'; - -import { - getVisibilityState, - VisibilityState, - getServiceWorkerStatus, - getEffectiveConnectionType, - isValidCustomAttributeName, - isValidCustomAttributeValue -} from './attributes_utils'; - -import '../../test/setup'; - -describe('Firebase Performance > attribute_utils', () => { - describe('#getServiceWorkerStatus', () => { - it('returns unsupported when service workers unsupported', () => { - stub(Api, 'getInstance').returns(({ - navigator: {} - } as unknown) as Api); - - expect(getServiceWorkerStatus()).to.be.eql(1); - }); - - it('returns controlled when service workers controlled', () => { - stub(Api, 'getInstance').returns(({ - navigator: { - serviceWorker: { - controller: {} - } - } - } as unknown) as Api); - - expect(getServiceWorkerStatus()).to.be.eql(2); - }); - - it('returns uncontrolled when service workers uncontrolled', () => { - stub(Api, 'getInstance').returns(({ - navigator: { - serviceWorker: {} - } - } as unknown) as Api); - - expect(getServiceWorkerStatus()).to.be.eql(3); - }); - }); - - describe('#getVisibilityState', () => { - afterEach(() => { - restore(); - }); - - it('returns visible when document is visible', () => { - stub(Api, 'getInstance').returns(({ - document: { - visibilityState: 'visible' - } - } as unknown) as Api); - expect(getVisibilityState()).to.be.eql(VisibilityState.VISIBLE); - }); - - it('returns hidden when document is hidden', () => { - stub(Api, 'getInstance').returns(({ - document: { - visibilityState: 'hidden' - } - } as unknown) as Api); - expect(getVisibilityState()).to.be.eql(VisibilityState.HIDDEN); - }); - - it('returns unknown when document is unknown', () => { - stub(Api, 'getInstance').returns(({ - document: { - visibilityState: 'unknown' - } - } as unknown) as Api); - expect(getVisibilityState()).to.be.eql(VisibilityState.UNKNOWN); - }); - }); - - describe('#getEffectiveConnectionType', () => { - afterEach(() => { - restore(); - }); - - it('returns EffectiveConnectionType.CONNECTION_SLOW_2G when slow-2g', () => { - stub(Api, 'getInstance').returns(({ - navigator: { - connection: { - effectiveType: 'slow-2g' - } - } - } as unknown) as Api); - expect(getEffectiveConnectionType()).to.be.eql(1); - }); - - it('returns EffectiveConnectionType.CONNECTION_2G when 2g', () => { - stub(Api, 'getInstance').returns(({ - navigator: { - connection: { - effectiveType: '2g' - } - } - } as unknown) as Api); - expect(getEffectiveConnectionType()).to.be.eql(2); - }); - - it('returns EffectiveConnectionType.CONNECTION_3G when 3g', () => { - stub(Api, 'getInstance').returns(({ - navigator: { - connection: { - effectiveType: '3g' - } - } - } as unknown) as Api); - expect(getEffectiveConnectionType()).to.be.eql(3); - }); - - it('returns EffectiveConnectionType.CONNECTION_4G when 4g', () => { - stub(Api, 'getInstance').returns(({ - navigator: { - connection: { - effectiveType: '4g' - } - } - } as unknown) as Api); - expect(getEffectiveConnectionType()).to.be.eql(4); - }); - - it('returns EffectiveConnectionType.UNKNOWN when unknown connection type', () => { - stub(Api, 'getInstance').returns(({ - navigator: { - connection: { - effectiveType: '5g' - } - } - } as unknown) as Api); - expect(getEffectiveConnectionType()).to.be.eql(0); - }); - - it('returns EffectiveConnectionType.UNKNOWN when no effective type', () => { - stub(Api, 'getInstance').returns(({ - navigator: { - connection: {} - } - } as unknown) as Api); - expect(getEffectiveConnectionType()).to.be.eql(0); - }); - }); - - describe('#isValidCustomAttributeName', () => { - it('returns true when name is valid', () => { - expect(isValidCustomAttributeName('validCustom_Attribute_Name')).to.be - .true; - }); - - it('returns false when name is blank', () => { - expect(isValidCustomAttributeName('')).to.be.false; - }); - - it('returns false when name is too long', () => { - expect( - isValidCustomAttributeName('invalid_custom_name_over_forty_characters') - ).to.be.false; - }); - - it('returns false when name starts with a reserved prefix', () => { - expect(isValidCustomAttributeName('firebase_invalidCustomName')).to.be - .false; - }); - - it('returns false when name does not begin with a letter', () => { - expect(isValidCustomAttributeName('_invalidCustomName')).to.be.false; - }); - - it('returns false when name contains prohibited characters', () => { - expect(isValidCustomAttributeName('invalidCustomName&')).to.be.false; - }); - }); - - describe('#isValidCustomAttributeValue', () => { - it('returns true when value is valid', () => { - expect(isValidCustomAttributeValue('valid_attribute_value')).to.be.true; - }); - - it('returns false when value is blank', () => { - expect(isValidCustomAttributeValue('')).to.be.false; - }); - - it('returns false when value is too long', () => { - const longAttributeValue = - 'too_long_attribute_value_over_one_hundred_characters_too_long_attribute_value_over_one_' + - 'hundred_charac'; - expect(isValidCustomAttributeValue(longAttributeValue)).to.be.false; - }); - }); -}); diff --git a/packages-exp/performance-exp/src/utils/attributes_utils.ts b/packages-exp/performance-exp/src/utils/attributes_utils.ts deleted file mode 100644 index ef3f499e23b..00000000000 --- a/packages-exp/performance-exp/src/utils/attributes_utils.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Api } from '../services/api_service'; - -// The values and orders of the following enums should not be changed. -const enum ServiceWorkerStatus { - UNKNOWN = 0, - UNSUPPORTED = 1, - CONTROLLED = 2, - UNCONTROLLED = 3 -} - -export enum VisibilityState { - UNKNOWN = 0, - VISIBLE = 1, - HIDDEN = 2 -} - -const enum EffectiveConnectionType { - UNKNOWN = 0, - CONNECTION_SLOW_2G = 1, - CONNECTION_2G = 2, - CONNECTION_3G = 3, - CONNECTION_4G = 4 -} - -/** - * NetworkInformation - * - * ref: https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation - */ -interface NetworkInformation { - readonly effectiveType?: 'slow-2g' | '2g' | '3g' | '4g'; -} - -interface NavigatorWithConnection extends Navigator { - readonly connection: NetworkInformation; -} - -const RESERVED_ATTRIBUTE_PREFIXES = ['firebase_', 'google_', 'ga_']; -const ATTRIBUTE_FORMAT_REGEX = new RegExp('^[a-zA-Z]\\w*$'); -const MAX_ATTRIBUTE_NAME_LENGTH = 40; -const MAX_ATTRIBUTE_VALUE_LENGTH = 100; - -export function getServiceWorkerStatus(): ServiceWorkerStatus { - const navigator = Api.getInstance().navigator; - if ('serviceWorker' in navigator) { - if (navigator.serviceWorker.controller) { - return ServiceWorkerStatus.CONTROLLED; - } else { - return ServiceWorkerStatus.UNCONTROLLED; - } - } else { - return ServiceWorkerStatus.UNSUPPORTED; - } -} - -export function getVisibilityState(): VisibilityState { - const document = Api.getInstance().document; - const visibilityState = document.visibilityState; - switch (visibilityState) { - case 'visible': - return VisibilityState.VISIBLE; - case 'hidden': - return VisibilityState.HIDDEN; - default: - return VisibilityState.UNKNOWN; - } -} - -export function getEffectiveConnectionType(): EffectiveConnectionType { - const navigator = Api.getInstance().navigator; - const navigatorConnection = (navigator as NavigatorWithConnection).connection; - const effectiveType = - navigatorConnection && navigatorConnection.effectiveType; - switch (effectiveType) { - case 'slow-2g': - return EffectiveConnectionType.CONNECTION_SLOW_2G; - case '2g': - return EffectiveConnectionType.CONNECTION_2G; - case '3g': - return EffectiveConnectionType.CONNECTION_3G; - case '4g': - return EffectiveConnectionType.CONNECTION_4G; - default: - return EffectiveConnectionType.UNKNOWN; - } -} - -export function isValidCustomAttributeName(name: string): boolean { - if (name.length === 0 || name.length > MAX_ATTRIBUTE_NAME_LENGTH) { - return false; - } - const matchesReservedPrefix = RESERVED_ATTRIBUTE_PREFIXES.some(prefix => - name.startsWith(prefix) - ); - return !matchesReservedPrefix && !!name.match(ATTRIBUTE_FORMAT_REGEX); -} - -export function isValidCustomAttributeValue(value: string): boolean { - return value.length !== 0 && value.length <= MAX_ATTRIBUTE_VALUE_LENGTH; -} diff --git a/packages-exp/performance-exp/src/utils/console_logger.ts b/packages-exp/performance-exp/src/utils/console_logger.ts deleted file mode 100644 index 2f97aeb386b..00000000000 --- a/packages-exp/performance-exp/src/utils/console_logger.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Logger, LogLevel } from '@firebase/logger'; -import { SERVICE_NAME } from '../constants'; - -export const consoleLogger = new Logger(SERVICE_NAME); -consoleLogger.logLevel = LogLevel.INFO; diff --git a/packages-exp/performance-exp/src/utils/errors.ts b/packages-exp/performance-exp/src/utils/errors.ts deleted file mode 100644 index 83e55b3eed5..00000000000 --- a/packages-exp/performance-exp/src/utils/errors.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorFactory } from '@firebase/util'; -import { SERVICE, SERVICE_NAME } from '../constants'; - -export const enum ErrorCode { - TRACE_STARTED_BEFORE = 'trace started', - TRACE_STOPPED_BEFORE = 'trace stopped', - NONPOSITIVE_TRACE_START_TIME = 'nonpositive trace startTime', - NONPOSITIVE_TRACE_DURATION = 'nonpositive trace duration', - NO_WINDOW = 'no window', - NO_APP_ID = 'no app id', - NO_PROJECT_ID = 'no project id', - NO_API_KEY = 'no api key', - INVALID_CC_LOG = 'invalid cc log', - FB_NOT_DEFAULT = 'FB not default', - RC_NOT_OK = 'RC response not ok', - INVALID_ATTRIBUTE_NAME = 'invalid attribute name', - INVALID_ATTRIBUTE_VALUE = 'invalid attribute value', - INVALID_CUSTOM_METRIC_NAME = 'invalid custom metric name', - INVALID_STRING_MERGER_PARAMETER = 'invalid String merger input', - ALREADY_INITIALIZED = 'already initialized' -} - -const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = { - [ErrorCode.TRACE_STARTED_BEFORE]: 'Trace {$traceName} was started before.', - [ErrorCode.TRACE_STOPPED_BEFORE]: 'Trace {$traceName} is not running.', - [ErrorCode.NONPOSITIVE_TRACE_START_TIME]: - 'Trace {$traceName} startTime should be positive.', - [ErrorCode.NONPOSITIVE_TRACE_DURATION]: - 'Trace {$traceName} duration should be positive.', - [ErrorCode.NO_WINDOW]: 'Window is not available.', - [ErrorCode.NO_APP_ID]: 'App id is not available.', - [ErrorCode.NO_PROJECT_ID]: 'Project id is not available.', - [ErrorCode.NO_API_KEY]: 'Api key is not available.', - [ErrorCode.INVALID_CC_LOG]: 'Attempted to queue invalid cc event', - [ErrorCode.FB_NOT_DEFAULT]: - 'Performance can only start when Firebase app instance is the default one.', - [ErrorCode.RC_NOT_OK]: 'RC response is not ok', - [ErrorCode.INVALID_ATTRIBUTE_NAME]: - 'Attribute name {$attributeName} is invalid.', - [ErrorCode.INVALID_ATTRIBUTE_VALUE]: - 'Attribute value {$attributeValue} is invalid.', - [ErrorCode.INVALID_CUSTOM_METRIC_NAME]: - 'Custom metric name {$customMetricName} is invalid', - [ErrorCode.INVALID_STRING_MERGER_PARAMETER]: - 'Input for String merger is invalid, contact support team to resolve.', - [ErrorCode.ALREADY_INITIALIZED]: - 'initializePerformance() has already been called with ' + - 'different options. To avoid this error, call initializePerformance() with the ' + - 'same options as when it was originally called, or call getPerformance() to return the' + - ' already initialized instance.' -}; - -interface ErrorParams { - [ErrorCode.TRACE_STARTED_BEFORE]: { traceName: string }; - [ErrorCode.TRACE_STOPPED_BEFORE]: { traceName: string }; - [ErrorCode.NONPOSITIVE_TRACE_START_TIME]: { traceName: string }; - [ErrorCode.NONPOSITIVE_TRACE_DURATION]: { traceName: string }; - [ErrorCode.INVALID_ATTRIBUTE_NAME]: { attributeName: string }; - [ErrorCode.INVALID_ATTRIBUTE_VALUE]: { attributeValue: string }; - [ErrorCode.INVALID_CUSTOM_METRIC_NAME]: { customMetricName: string }; -} - -export const ERROR_FACTORY = new ErrorFactory( - SERVICE, - SERVICE_NAME, - ERROR_DESCRIPTION_MAP -); diff --git a/packages-exp/performance-exp/src/utils/metric_utils.test.ts b/packages-exp/performance-exp/src/utils/metric_utils.test.ts deleted file mode 100644 index fea213911f7..00000000000 --- a/packages-exp/performance-exp/src/utils/metric_utils.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF unknown KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; - -import { isValidMetricName } from './metric_utils'; -import { - FIRST_PAINT_COUNTER_NAME, - FIRST_CONTENTFUL_PAINT_COUNTER_NAME, - FIRST_INPUT_DELAY_COUNTER_NAME -} from '../constants'; -import '../../test/setup'; - -describe('Firebase Performance > metric_utils', () => { - describe('#isValidMetricName', () => { - it('returns true when name is valid', () => { - expect(isValidMetricName('validCustom_Metric_Name')).to.be.true; - }); - - it('returns false when name is blank', () => { - expect(isValidMetricName('')).to.be.false; - }); - - it('returns false when name is too long', () => { - const longMetricName = - 'too_long_metric_name_over_one_hundred_characters_too_long_metric_name_over_one_' + - 'hundred_characters_too'; - expect(isValidMetricName(longMetricName)).to.be.false; - }); - - it('returns false when name starts with a reserved prefix', () => { - expect(isValidMetricName('_invalidMetricName')).to.be.false; - }); - - it('returns true for first paint metric', () => { - expect( - isValidMetricName(FIRST_PAINT_COUNTER_NAME, '_wt_http://example.com') - ).to.be.true; - }); - - it('returns true for first contentful paint metric', () => { - expect( - isValidMetricName( - FIRST_CONTENTFUL_PAINT_COUNTER_NAME, - '_wt_http://example.com' - ) - ).to.be.true; - }); - - it('returns true for first input delay metric', () => { - expect( - isValidMetricName( - FIRST_INPUT_DELAY_COUNTER_NAME, - '_wt_http://example.com' - ) - ).to.be.true; - }); - - it('returns false if first paint metric name is used outside of page load traces', () => { - expect(isValidMetricName(FIRST_PAINT_COUNTER_NAME, 'some_randome_trace')) - .to.be.false; - }); - - it('returns false if first contentful paint metric name is used outside of page load traces', () => { - expect( - isValidMetricName( - FIRST_CONTENTFUL_PAINT_COUNTER_NAME, - 'some_randome_trace' - ) - ).to.be.false; - }); - - it('returns false if first input delay metric name is used outside of page load traces', () => { - expect( - isValidMetricName(FIRST_INPUT_DELAY_COUNTER_NAME, 'some_randome_trace') - ).to.be.false; - }); - }); -}); diff --git a/packages-exp/performance-exp/src/utils/metric_utils.ts b/packages-exp/performance-exp/src/utils/metric_utils.ts deleted file mode 100644 index 9bbc4886aef..00000000000 --- a/packages-exp/performance-exp/src/utils/metric_utils.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - FIRST_PAINT_COUNTER_NAME, - FIRST_CONTENTFUL_PAINT_COUNTER_NAME, - FIRST_INPUT_DELAY_COUNTER_NAME, - OOB_TRACE_PAGE_LOAD_PREFIX -} from '../constants'; -import { consoleLogger } from '../utils/console_logger'; - -const MAX_METRIC_NAME_LENGTH = 100; -const RESERVED_AUTO_PREFIX = '_'; -const oobMetrics = [ - FIRST_PAINT_COUNTER_NAME, - FIRST_CONTENTFUL_PAINT_COUNTER_NAME, - FIRST_INPUT_DELAY_COUNTER_NAME -]; - -/** - * Returns true if the metric is custom and does not start with reserved prefix, or if - * the metric is one of out of the box page load trace metrics. - */ -export function isValidMetricName(name: string, traceName?: string): boolean { - if (name.length === 0 || name.length > MAX_METRIC_NAME_LENGTH) { - return false; - } - return ( - (traceName && - traceName.startsWith(OOB_TRACE_PAGE_LOAD_PREFIX) && - oobMetrics.indexOf(name) > -1) || - !name.startsWith(RESERVED_AUTO_PREFIX) - ); -} - -/** - * Converts the provided value to an integer value to be used in case of a metric. - * @param providedValue Provided number value of the metric that needs to be converted to an integer. - * - * @returns Converted integer number to be set for the metric. - */ -export function convertMetricValueToInteger(providedValue: number): number { - const valueAsInteger: number = Math.floor(providedValue); - if (valueAsInteger < providedValue) { - consoleLogger.info( - `Metric value should be an Integer, setting the value as : ${valueAsInteger}.` - ); - } - return valueAsInteger; -} diff --git a/packages-exp/performance-exp/src/utils/string_merger.test.ts b/packages-exp/performance-exp/src/utils/string_merger.test.ts deleted file mode 100644 index a82799f315e..00000000000 --- a/packages-exp/performance-exp/src/utils/string_merger.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF unknown KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; - -import { mergeStrings } from './string_merger'; -import { FirebaseError } from '@firebase/util'; -import '../../test/setup'; - -describe('Firebase Performance > string_merger', () => { - describe('#mergeStrings', () => { - it('Throws exception when string length has | diff | > 1', () => { - expect(() => mergeStrings('', '123')).to.throw( - FirebaseError, - 'performance/invalid String merger input' - ); - }); - - it('returns empty string when both inputs are empty', () => { - expect(mergeStrings('', '')).equal(''); - }); - - it('returns merge result string when both inputs have same length', () => { - expect(mergeStrings('12345', 'abcde')).equal('1a2b3c4d5e'); - }); - - it('returns merge result string when input length diff == 1', () => { - expect(() => mergeStrings('1234', 'abcde')).to.throw( - FirebaseError, - 'performance/invalid String merger input' - ); - }); - - it('returns merge result string when input length diff == -1', () => { - expect(mergeStrings('12345', 'abcd')).equal('1a2b3c4d5'); - }); - }); -}); diff --git a/packages-exp/performance-exp/src/utils/string_merger.ts b/packages-exp/performance-exp/src/utils/string_merger.ts deleted file mode 100644 index 620488a7416..00000000000 --- a/packages-exp/performance-exp/src/utils/string_merger.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ERROR_FACTORY, ErrorCode } from './errors'; - -export function mergeStrings(part1: string, part2: string): string { - const sizeDiff = part1.length - part2.length; - if (sizeDiff < 0 || sizeDiff > 1) { - throw ERROR_FACTORY.create(ErrorCode.INVALID_STRING_MERGER_PARAMETER); - } - - const resultArray = []; - for (let i = 0; i < part1.length; i++) { - resultArray.push(part1.charAt(i)); - if (part2.length > i) { - resultArray.push(part2.charAt(i)); - } - } - - return resultArray.join(''); -} diff --git a/packages-exp/performance-exp/tsconfig.json b/packages-exp/performance-exp/tsconfig.json deleted file mode 100644 index a3c61dfddfd..00000000000 --- a/packages-exp/performance-exp/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "resolveJsonModule": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true - }, - "exclude": ["dist/**/*"] -} diff --git a/packages-exp/remote-config-compat/.eslintrc.js b/packages-exp/remote-config-compat/.eslintrc.js deleted file mode 100644 index ca80aa0f69a..00000000000 --- a/packages-exp/remote-config-compat/.eslintrc.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = { - extends: '../../config/.eslintrc.js', - parserOptions: { - project: 'tsconfig.json', - // to make vscode-eslint work with monorepo - // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 - tsconfigRootDir: __dirname - } -}; diff --git a/packages-exp/remote-config-compat/rollup.config.js b/packages-exp/remote-config-compat/rollup.config.js deleted file mode 100644 index 4df9d8a2f48..00000000000 --- a/packages-exp/remote-config-compat/rollup.config.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared.js'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-compat/rollup.config.release.js b/packages-exp/remote-config-compat/rollup.config.release.js deleted file mode 100644 index b0b1dc5154a..00000000000 --- a/packages-exp/remote-config-compat/rollup.config.release.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared.js'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-compat/rollup.shared.js b/packages-exp/remote-config-compat/rollup.shared.js deleted file mode 100644 index 3a873a01807..00000000000 --- a/packages-exp/remote-config-compat/rollup.shared.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/remote-config' -]; - -export const es5BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -export const es2017BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/remote-config-compat/test/setup.ts b/packages-exp/remote-config-compat/test/setup.ts deleted file mode 100644 index b1e3136529f..00000000000 --- a/packages-exp/remote-config-compat/test/setup.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { use } from 'chai'; -import { restore } from 'sinon'; -import * as sinonChai from 'sinon-chai'; - -use(sinonChai); - -afterEach(async () => { - restore(); -}); diff --git a/packages-exp/remote-config-exp/.eslintrc.js b/packages-exp/remote-config-exp/.eslintrc.js deleted file mode 100644 index 5a8c4b909c2..00000000000 --- a/packages-exp/remote-config-exp/.eslintrc.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = { - 'extends': '../../config/.eslintrc.js', - 'parserOptions': { - project: 'tsconfig.json', - // to make vscode-eslint work with monorepo - // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 - tsconfigRootDir: __dirname - } -}; diff --git a/packages-exp/remote-config-exp/.npmignore b/packages-exp/remote-config-exp/.npmignore deleted file mode 100644 index 6de0b6d2896..00000000000 --- a/packages-exp/remote-config-exp/.npmignore +++ /dev/null @@ -1 +0,0 @@ -# This file is left intentionally blank \ No newline at end of file diff --git a/packages-exp/remote-config-exp/README.md b/packages-exp/remote-config-exp/README.md deleted file mode 100644 index 1b21bbecc51..00000000000 --- a/packages-exp/remote-config-exp/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# @firebase/remote-config - -This is the [Remote Config](https://firebase.google.com/docs/remote-config/) component of the -[Firebase JS SDK](https://www.npmjs.com/package/firebase). - -**This package is not intended for direct usage, and should only be used via the officially -supported [firebase](https://www.npmjs.com/package/firebase) package.** - -## Contributing - -Setup: - -1. Run `yarn` in repo root - -Format: - -1. Run `yarn prettier` in RC package - -Unit test: - -1. Run `yarn test` in RC package - -End-to-end test: - -1. Run `yarn build` in RC package -1. Run `yarn build` in Firebase package -1. Open test_app/index.html in a browser diff --git a/packages-exp/remote-config-exp/package.json b/packages-exp/remote-config-exp/package.json deleted file mode 100644 index c0ee138c302..00000000000 --- a/packages-exp/remote-config-exp/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "@firebase/remote-config-exp", - "version": "0.0.900", - "description": "The Remote Config package of the Firebase JS SDK", - "author": "Firebase (https://firebase.google.com/)", - "private": true, - "main": "dist/index.cjs.js", - "browser": "dist/index.esm2017.js", - "module": "dist/index.esm2017.js", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c && yarn api-report", - "build:deps": "lerna run --scope @firebase/remote-config-exp --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", - "dev": "rollup -c -w", - "test": "run-p lint test:browser", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", - "test:browser": "karma start --single-run", - "test:debug": "karma start --browsers=Chrome --auto-watch", - "prettier": "prettier --write '{src,test}/**/*.{js,ts}'", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc", - "typings:public": "node ../../scripts/exp/use_typings.js ./dist/remote-config-exp-public.d.ts", - "typings:internal": "node ../../scripts/exp/use_typings.js ./dist/src/index.d.ts" - }, - "peerDependencies": { - "@firebase/app-exp": "0.x" - }, - "dependencies": { - "@firebase/installations-exp": "0.0.900", - "@firebase/logger": "0.2.6", - "@firebase/util": "1.3.0", - "@firebase/component": "0.5.6", - "tslib": "^2.1.0" - }, - "license": "Apache-2.0", - "devDependencies": { - "@firebase/app-exp": "0.0.900", - "rollup": "2.52.2", - "rollup-plugin-typescript2": "0.30.0", - "typescript": "4.2.2" - }, - "repository": { - "directory": "packages/remote-config", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "typings": "dist/src/index.d.ts", - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - }, - "esm5": "dist/index.esm.js" -} \ No newline at end of file diff --git a/packages-exp/remote-config-exp/rollup.config.js b/packages-exp/remote-config-exp/rollup.config.js deleted file mode 100644 index b57e5ad73ee..00000000000 --- a/packages-exp/remote-config-exp/rollup.config.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; // Enables package.json import in TypeScript. -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ preferConst: true }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-exp/rollup.config.release.js b/packages-exp/remote-config-exp/rollup.config.release.js deleted file mode 100644 index 7e38601f87c..00000000000 --- a/packages-exp/remote-config-exp/rollup.config.release.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import json from '@rollup/plugin-json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - clean: true, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: (id, external) => id === '@firebase/installations' - } -})); - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - clean: true, - transformers: [importPathTransformer] - }), - json({ - preferConst: true - }) -]; - -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: (id, external) => id === '@firebase/installations' - } -})); - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-exp/rollup.shared.js b/packages-exp/remote-config-exp/rollup.shared.js deleted file mode 100644 index 397949ded6b..00000000000 --- a/packages-exp/remote-config-exp/rollup.shared.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -export const es5BuildsNoPlugin = [ - /** - * Browser Builds - */ - { - input: 'src/index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.esm5, format: 'es', sourcemap: true } - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; - -/** - * ES2017 Builds - */ -export const es2017BuildsNoPlugin = [ - { - /** - * Browser Build - */ - input: 'src/index.ts', - output: { - file: pkg.browser, - format: 'es', - sourcemap: true - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - } -]; diff --git a/packages-exp/remote-config-exp/src/client/caching_client.ts b/packages-exp/remote-config-exp/src/client/caching_client.ts deleted file mode 100644 index aea61acfd1f..00000000000 --- a/packages-exp/remote-config-exp/src/client/caching_client.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { StorageCache } from '../storage/storage_cache'; -import { - FetchResponse, - RemoteConfigFetchClient, - FetchRequest -} from './remote_config_fetch_client'; -import { Storage } from '../storage/storage'; -import { Logger } from '@firebase/logger'; - -/** - * Implements the {@link RemoteConfigClient} abstraction with success response caching. - * - *

Comparable to the browser's Cache API for responses, but the Cache API requires a Service - * Worker, which requires HTTPS, which would significantly complicate SDK installation. Also, the - * Cache API doesn't support matching entries by time. - */ -export class CachingClient implements RemoteConfigFetchClient { - constructor( - private readonly client: RemoteConfigFetchClient, - private readonly storage: Storage, - private readonly storageCache: StorageCache, - private readonly logger: Logger - ) {} - - /** - * Returns true if the age of the cached fetched configs is less than or equal to - * {@link Settings#minimumFetchIntervalInSeconds}. - * - *

This is comparable to passing `headers = { 'Cache-Control': max-age }` to the - * native Fetch API. - * - *

Visible for testing. - */ - isCachedDataFresh( - cacheMaxAgeMillis: number, - lastSuccessfulFetchTimestampMillis: number | undefined - ): boolean { - // Cache can only be fresh if it's populated. - if (!lastSuccessfulFetchTimestampMillis) { - this.logger.debug('Config fetch cache check. Cache unpopulated.'); - return false; - } - - // Calculates age of cache entry. - const cacheAgeMillis = Date.now() - lastSuccessfulFetchTimestampMillis; - - const isCachedDataFresh = cacheAgeMillis <= cacheMaxAgeMillis; - - this.logger.debug( - 'Config fetch cache check.' + - ` Cache age millis: ${cacheAgeMillis}.` + - ` Cache max age millis (minimumFetchIntervalMillis setting): ${cacheMaxAgeMillis}.` + - ` Is cache hit: ${isCachedDataFresh}.` - ); - - return isCachedDataFresh; - } - - async fetch(request: FetchRequest): Promise { - // Reads from persisted storage to avoid cache miss if callers don't wait on initialization. - const [ - lastSuccessfulFetchTimestampMillis, - lastSuccessfulFetchResponse - ] = await Promise.all([ - this.storage.getLastSuccessfulFetchTimestampMillis(), - this.storage.getLastSuccessfulFetchResponse() - ]); - - // Exits early on cache hit. - if ( - lastSuccessfulFetchResponse && - this.isCachedDataFresh( - request.cacheMaxAgeMillis, - lastSuccessfulFetchTimestampMillis - ) - ) { - return lastSuccessfulFetchResponse; - } - - // Deviates from pure decorator by not honoring a passed ETag since we don't have a public API - // that allows the caller to pass an ETag. - request.eTag = - lastSuccessfulFetchResponse && lastSuccessfulFetchResponse.eTag; - - // Falls back to service on cache miss. - const response = await this.client.fetch(request); - - // Fetch throws for non-success responses, so success is guaranteed here. - - const storageOperations = [ - // Uses write-through cache for consistency with synchronous public API. - this.storageCache.setLastSuccessfulFetchTimestampMillis(Date.now()) - ]; - - if (response.status === 200) { - // Caches response only if it has changed, ie non-304 responses. - storageOperations.push( - this.storage.setLastSuccessfulFetchResponse(response) - ); - } - - await Promise.all(storageOperations); - - return response; - } -} diff --git a/packages-exp/remote-config-exp/src/client/remote_config_fetch_client.ts b/packages-exp/remote-config-exp/src/client/remote_config_fetch_client.ts deleted file mode 100644 index 25e00299855..00000000000 --- a/packages-exp/remote-config-exp/src/client/remote_config_fetch_client.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Defines a client, as in https://en.wikipedia.org/wiki/Client%E2%80%93server_model, for the - * Remote Config server (https://firebase.google.com/docs/reference/remote-config/rest). - * - *

Abstracts throttle, response cache and network implementation details. - * - *

Modeled after the native {@link GlobalFetch} interface, which is relatively modern and - * convenient, but simplified for Remote Config's use case. - * - * Disambiguation: {@link GlobalFetch} interface and the Remote Config service define "fetch" - * methods. The RestClient uses the former to make HTTP calls. This interface abstracts the latter. - */ -export interface RemoteConfigFetchClient { - /** - * @throws if response status is not 200 or 304. - */ - fetch(request: FetchRequest): Promise; -} - -/** - * Defines a self-descriptive reference for config key-value pairs. - */ -export interface FirebaseRemoteConfigObject { - [key: string]: string; -} - -/** - * Shims a minimal AbortSignal. - * - *

AbortController's AbortSignal conveniently decouples fetch timeout logic from other aspects - * of networking, such as retries. Firebase doesn't use AbortController enough to justify a - * polyfill recommendation, like we do with the Fetch API, but this minimal shim can easily be - * swapped out if/when we do. - */ -export class RemoteConfigAbortSignal { - listeners: Array<() => void> = []; - addEventListener(listener: () => void): void { - this.listeners.push(listener); - } - abort(): void { - this.listeners.forEach(listener => listener()); - } -} - -/** - * Defines per-request inputs for the Remote Config fetch request. - * - *

Modeled after the native {@link Request} interface, but simplified for Remote Config's - * use case. - */ -export interface FetchRequest { - /** - * Uses cached config if it is younger than this age. - * - *

Required because it's defined by settings, which always have a value. - * - *

Comparable to passing `headers = { 'Cache-Control': max-age }` to the native - * Fetch API. - */ - cacheMaxAgeMillis: number; - - /** - * An event bus for the signal to abort a request. - * - *

Required because all requests should be abortable. - * - *

Comparable to the native - * Fetch API's "signal" field on its request configuration object - * https://fetch.spec.whatwg.org/#dom-requestinit-signal. - * - *

Disambiguation: Remote Config commonly refers to API inputs as - * "signals". See the private ConfigFetchRequestBody interface for those: - * http://google3/firebase/remote_config/web/src/core/rest_client.ts?l=14&rcl=255515243. - */ - signal: RemoteConfigAbortSignal; - - /** - * The ETag header value from the last response. - * - *

Optional in case this is the first request. - * - *

Comparable to passing `headers = { 'If-None-Match': }` to the native Fetch API. - */ - eTag?: string; -} - -/** - * Defines a successful response (200 or 304). - * - *

Modeled after the native {@link Response} interface, but simplified for Remote Config's - * use case. - */ -export interface FetchResponse { - /** - * The HTTP status, which is useful for differentiating success responses with data from - * those without. - * - *

{@link RemoteConfigClient} is modeled after the native {@link GlobalFetch} interface, so - * HTTP status is first-class. - * - *

Disambiguation: the fetch response returns a legacy "state" value that is redundant with the - * HTTP status code. The former is normalized into the latter. - */ - status: number; - - /** - * Defines the ETag response header value. - * - *

Only defined for 200 and 304 responses. - */ - eTag?: string; - - /** - * Defines the map of parameters returned as "entries" in the fetch response body. - * - *

Only defined for 200 responses. - */ - config?: FirebaseRemoteConfigObject; - - // Note: we're not extracting experiment metadata until - // ABT and Analytics have Web SDKs. -} diff --git a/packages-exp/remote-config-exp/src/client/rest_client.ts b/packages-exp/remote-config-exp/src/client/rest_client.ts deleted file mode 100644 index 4d1d19c0b4e..00000000000 --- a/packages-exp/remote-config-exp/src/client/rest_client.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - FetchResponse, - RemoteConfigFetchClient, - FirebaseRemoteConfigObject, - FetchRequest -} from './remote_config_fetch_client'; -import { ERROR_FACTORY, ErrorCode } from '../errors'; -import { getUserLanguage } from '../language'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; - -/** - * Defines request body parameters required to call the fetch API: - * https://firebase.google.com/docs/reference/remote-config/rest - * - *

Not exported because this file encapsulates REST API specifics. - * - *

Not passing User Properties because Analytics' source of truth on Web is server-side. - */ -interface FetchRequestBody { - // Disables camelcase linting for request body params. - /* eslint-disable camelcase*/ - sdk_version: string; - app_instance_id: string; - app_instance_id_token: string; - app_id: string; - language_code: string; - /* eslint-enable camelcase */ -} - -/** - * Implements the Client abstraction for the Remote Config REST API. - */ -export class RestClient implements RemoteConfigFetchClient { - constructor( - private readonly firebaseInstallations: _FirebaseInstallationsInternal, - private readonly sdkVersion: string, - private readonly namespace: string, - private readonly projectId: string, - private readonly apiKey: string, - private readonly appId: string - ) {} - - /** - * Fetches from the Remote Config REST API. - * - * @throws a {@link ErrorCode.FETCH_NETWORK} error if {@link GlobalFetch#fetch} can't - * connect to the network. - * @throws a {@link ErrorCode.FETCH_PARSE} error if {@link Response#json} can't parse the - * fetch response. - * @throws a {@link ErrorCode.FETCH_STATUS} error if the service returns an HTTP error status. - */ - async fetch(request: FetchRequest): Promise { - const [installationId, installationToken] = await Promise.all([ - this.firebaseInstallations.getId(), - this.firebaseInstallations.getToken() - ]); - - const urlBase = - window.FIREBASE_REMOTE_CONFIG_URL_BASE || - 'https://firebaseremoteconfig.googleapis.com'; - - const url = `${urlBase}/v1/projects/${this.projectId}/namespaces/${this.namespace}:fetch?key=${this.apiKey}`; - - const headers = { - 'Content-Type': 'application/json', - 'Content-Encoding': 'gzip', - // Deviates from pure decorator by not passing max-age header since we don't currently have - // service behavior using that header. - 'If-None-Match': request.eTag || '*' - }; - - const requestBody: FetchRequestBody = { - /* eslint-disable camelcase */ - sdk_version: this.sdkVersion, - app_instance_id: installationId, - app_instance_id_token: installationToken, - app_id: this.appId, - language_code: getUserLanguage() - /* eslint-enable camelcase */ - }; - - const options = { - method: 'POST', - headers, - body: JSON.stringify(requestBody) - }; - - // This logic isn't REST-specific, but shimming abort logic isn't worth another decorator. - const fetchPromise = fetch(url, options); - const timeoutPromise = new Promise((_resolve, reject) => { - // Maps async event listener to Promise API. - request.signal.addEventListener(() => { - // Emulates https://heycam.github.io/webidl/#aborterror - const error = new Error('The operation was aborted.'); - error.name = 'AbortError'; - reject(error); - }); - }); - - let response; - try { - await Promise.race([fetchPromise, timeoutPromise]); - response = await fetchPromise; - } catch (originalError) { - let errorCode = ErrorCode.FETCH_NETWORK; - if (originalError.name === 'AbortError') { - errorCode = ErrorCode.FETCH_TIMEOUT; - } - throw ERROR_FACTORY.create(errorCode, { - originalErrorMessage: originalError.message - }); - } - - let status = response.status; - - // Normalizes nullable header to optional. - const responseEtag = response.headers.get('ETag') || undefined; - - let config: FirebaseRemoteConfigObject | undefined; - let state: string | undefined; - - // JSON parsing throws SyntaxError if the response body isn't a JSON string. - // Requesting application/json and checking for a 200 ensures there's JSON data. - if (response.status === 200) { - let responseBody; - try { - responseBody = await response.json(); - } catch (originalError) { - throw ERROR_FACTORY.create(ErrorCode.FETCH_PARSE, { - originalErrorMessage: originalError.message - }); - } - config = responseBody['entries']; - state = responseBody['state']; - } - - // Normalizes based on legacy state. - if (state === 'INSTANCE_STATE_UNSPECIFIED') { - status = 500; - } else if (state === 'NO_CHANGE') { - status = 304; - } else if (state === 'NO_TEMPLATE' || state === 'EMPTY_CONFIG') { - // These cases can be fixed remotely, so normalize to safe value. - config = {}; - } - - // Normalize to exception-based control flow for non-success cases. - // Encapsulates HTTP specifics in this class as much as possible. Status is still the best for - // differentiating success states (200 from 304; the state body param is undefined in a - // standard 304). - if (status !== 304 && status !== 200) { - throw ERROR_FACTORY.create(ErrorCode.FETCH_STATUS, { - httpStatus: status - }); - } - - return { status, eTag: responseEtag, config }; - } -} diff --git a/packages-exp/remote-config-exp/src/client/retrying_client.ts b/packages-exp/remote-config-exp/src/client/retrying_client.ts deleted file mode 100644 index fe1737023df..00000000000 --- a/packages-exp/remote-config-exp/src/client/retrying_client.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - RemoteConfigAbortSignal, - RemoteConfigFetchClient, - FetchResponse, - FetchRequest -} from './remote_config_fetch_client'; -import { ThrottleMetadata, Storage } from '../storage/storage'; -import { ErrorCode, ERROR_FACTORY } from '../errors'; -import { FirebaseError, calculateBackoffMillis } from '@firebase/util'; - -/** - * Supports waiting on a backoff by: - * - *

    - *
  • Promisifying setTimeout, so we can set a timeout in our Promise chain
  • - *
  • Listening on a signal bus for abort events, just like the Fetch API
  • - *
  • Failing in the same way the Fetch API fails, so timing out a live request and a throttled - * request appear the same.
  • - *
- * - *

Visible for testing. - */ -export function setAbortableTimeout( - signal: RemoteConfigAbortSignal, - throttleEndTimeMillis: number -): Promise { - return new Promise((resolve, reject) => { - // Derives backoff from given end time, normalizing negative numbers to zero. - const backoffMillis = Math.max(throttleEndTimeMillis - Date.now(), 0); - - const timeout = setTimeout(resolve, backoffMillis); - - // Adds listener, rather than sets onabort, because signal is a shared object. - signal.addEventListener(() => { - clearTimeout(timeout); - - // If the request completes before this timeout, the rejection has no effect. - reject( - ERROR_FACTORY.create(ErrorCode.FETCH_THROTTLE, { - throttleEndTimeMillis - }) - ); - }); - }); -} - -type RetriableError = FirebaseError & { customData: { httpStatus: string } }; -/** - * Returns true if the {@link Error} indicates a fetch request may succeed later. - */ -function isRetriableError(e: Error): e is RetriableError { - if (!(e instanceof FirebaseError) || !e.customData) { - return false; - } - - // Uses string index defined by ErrorData, which FirebaseError implements. - const httpStatus = Number(e.customData['httpStatus']); - - return ( - httpStatus === 429 || - httpStatus === 500 || - httpStatus === 503 || - httpStatus === 504 - ); -} - -/** - * Decorates a Client with retry logic. - * - *

Comparable to CachingClient, but uses backoff logic instead of cache max age and doesn't cache - * responses (because the SDK has no use for error responses). - */ -export class RetryingClient implements RemoteConfigFetchClient { - constructor( - private readonly client: RemoteConfigFetchClient, - private readonly storage: Storage - ) {} - - async fetch(request: FetchRequest): Promise { - const throttleMetadata = (await this.storage.getThrottleMetadata()) || { - backoffCount: 0, - throttleEndTimeMillis: Date.now() - }; - - return this.attemptFetch(request, throttleMetadata); - } - - /** - * A recursive helper for attempting a fetch request repeatedly. - * - * @throws any non-retriable errors. - */ - async attemptFetch( - request: FetchRequest, - { throttleEndTimeMillis, backoffCount }: ThrottleMetadata - ): Promise { - // Starts with a (potentially zero) timeout to support resumption from stored state. - // Ensures the throttle end time is honored if the last attempt timed out. - // Note the SDK will never make a request if the fetch timeout expires at this point. - await setAbortableTimeout(request.signal, throttleEndTimeMillis); - - try { - const response = await this.client.fetch(request); - - // Note the SDK only clears throttle state if response is success or non-retriable. - await this.storage.deleteThrottleMetadata(); - - return response; - } catch (e) { - if (!isRetriableError(e)) { - throw e; - } - - // Increments backoff state. - const throttleMetadata = { - throttleEndTimeMillis: - Date.now() + calculateBackoffMillis(backoffCount), - backoffCount: backoffCount + 1 - }; - - // Persists state. - await this.storage.setThrottleMetadata(throttleMetadata); - - return this.attemptFetch(request, throttleMetadata); - } - } -} diff --git a/packages-exp/remote-config-exp/src/constants.ts b/packages-exp/remote-config-exp/src/constants.ts deleted file mode 100644 index 6bc7d1d5547..00000000000 --- a/packages-exp/remote-config-exp/src/constants.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const RC_COMPONENT_NAME = 'remote-config-exp'; diff --git a/packages-exp/remote-config-exp/src/errors.ts b/packages-exp/remote-config-exp/src/errors.ts deleted file mode 100644 index d4be9a09f76..00000000000 --- a/packages-exp/remote-config-exp/src/errors.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorFactory, FirebaseError } from '@firebase/util'; - -export const enum ErrorCode { - REGISTRATION_WINDOW = 'registration-window', - REGISTRATION_PROJECT_ID = 'registration-project-id', - REGISTRATION_API_KEY = 'registration-api-key', - REGISTRATION_APP_ID = 'registration-app-id', - STORAGE_OPEN = 'storage-open', - STORAGE_GET = 'storage-get', - STORAGE_SET = 'storage-set', - STORAGE_DELETE = 'storage-delete', - FETCH_NETWORK = 'fetch-client-network', - FETCH_TIMEOUT = 'fetch-timeout', - FETCH_THROTTLE = 'fetch-throttle', - FETCH_PARSE = 'fetch-client-parse', - FETCH_STATUS = 'fetch-status' -} - -const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = { - [ErrorCode.REGISTRATION_WINDOW]: - 'Undefined window object. This SDK only supports usage in a browser environment.', - [ErrorCode.REGISTRATION_PROJECT_ID]: - 'Undefined project identifier. Check Firebase app initialization.', - [ErrorCode.REGISTRATION_API_KEY]: - 'Undefined API key. Check Firebase app initialization.', - [ErrorCode.REGISTRATION_APP_ID]: - 'Undefined app identifier. Check Firebase app initialization.', - [ErrorCode.STORAGE_OPEN]: - 'Error thrown when opening storage. Original error: {$originalErrorMessage}.', - [ErrorCode.STORAGE_GET]: - 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.', - [ErrorCode.STORAGE_SET]: - 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.', - [ErrorCode.STORAGE_DELETE]: - 'Error thrown when deleting from storage. Original error: {$originalErrorMessage}.', - [ErrorCode.FETCH_NETWORK]: - 'Fetch client failed to connect to a network. Check Internet connection.' + - ' Original error: {$originalErrorMessage}.', - [ErrorCode.FETCH_TIMEOUT]: - 'The config fetch request timed out. ' + - ' Configure timeout using "fetchTimeoutMillis" SDK setting.', - [ErrorCode.FETCH_THROTTLE]: - 'The config fetch request timed out while in an exponential backoff state.' + - ' Configure timeout using "fetchTimeoutMillis" SDK setting.' + - ' Unix timestamp in milliseconds when fetch request throttling ends: {$throttleEndTimeMillis}.', - [ErrorCode.FETCH_PARSE]: - 'Fetch client could not parse response.' + - ' Original error: {$originalErrorMessage}.', - [ErrorCode.FETCH_STATUS]: - 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.' -}; - -// Note this is effectively a type system binding a code to params. This approach overlaps with the -// role of TS interfaces, but works well for a few reasons: -// 1) JS is unaware of TS interfaces, eg we can't test for interface implementation in JS -// 2) callers should have access to a human-readable summary of the error and this interpolates -// params into an error message; -// 3) callers should be able to programmatically access data associated with an error, which -// ErrorData provides. -interface ErrorParams { - [ErrorCode.STORAGE_OPEN]: { originalErrorMessage: string | undefined }; - [ErrorCode.STORAGE_GET]: { originalErrorMessage: string | undefined }; - [ErrorCode.STORAGE_SET]: { originalErrorMessage: string | undefined }; - [ErrorCode.STORAGE_DELETE]: { originalErrorMessage: string | undefined }; - [ErrorCode.FETCH_NETWORK]: { originalErrorMessage: string }; - [ErrorCode.FETCH_THROTTLE]: { throttleEndTimeMillis: number }; - [ErrorCode.FETCH_PARSE]: { originalErrorMessage: string }; - [ErrorCode.FETCH_STATUS]: { httpStatus: number }; -} - -export const ERROR_FACTORY = new ErrorFactory( - 'remoteconfig' /* service */, - 'Remote Config' /* service name */, - ERROR_DESCRIPTION_MAP -); - -// Note how this is like typeof/instanceof, but for ErrorCode. -export function hasErrorCode(e: Error, errorCode: ErrorCode): boolean { - return e instanceof FirebaseError && e.code.indexOf(errorCode) !== -1; -} diff --git a/packages-exp/remote-config-exp/src/language.ts b/packages-exp/remote-config-exp/src/language.ts deleted file mode 100644 index 9c44ee275bf..00000000000 --- a/packages-exp/remote-config-exp/src/language.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Attempts to get the most accurate browser language setting. - * - *

Adapted from getUserLanguage in packages/auth/src/utils.js for TypeScript. - * - *

Defers default language specification to server logic for consistency. - * - * @param navigatorLanguage Enables tests to override read-only {@link NavigatorLanguage}. - */ -export function getUserLanguage( - navigatorLanguage: NavigatorLanguage = navigator -): string { - return ( - // Most reliable, but only supported in Chrome/Firefox. - (navigatorLanguage.languages && navigatorLanguage.languages[0]) || - // Supported in most browsers, but returns the language of the browser - // UI, not the language set in browser settings. - navigatorLanguage.language - // Polyfill otherwise. - ); -} diff --git a/packages-exp/remote-config-exp/src/remote_config.ts b/packages-exp/remote-config-exp/src/remote_config.ts deleted file mode 100644 index 8e0b23c73d6..00000000000 --- a/packages-exp/remote-config-exp/src/remote_config.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp } from '@firebase/app-exp'; -import { - RemoteConfig as RemoteConfigType, - FetchStatus, - RemoteConfigSettings -} from './public_types'; -import { StorageCache } from './storage/storage_cache'; -import { RemoteConfigFetchClient } from './client/remote_config_fetch_client'; -import { Storage } from './storage/storage'; -import { Logger } from '@firebase/logger'; - -const DEFAULT_FETCH_TIMEOUT_MILLIS = 60 * 1000; // One minute -const DEFAULT_CACHE_MAX_AGE_MILLIS = 12 * 60 * 60 * 1000; // Twelve hours. - -/** - * Encapsulates business logic mapping network and storage dependencies to the public SDK API. - * - * See {@link https://github.com/FirebasePrivate/firebase-js-sdk/blob/master/packages/firebase/index.d.ts|interface documentation} for method descriptions. - */ -export class RemoteConfig implements RemoteConfigType { - /** - * Tracks completion of initialization promise. - * @internal - */ - _isInitializationComplete = false; - - /** - * De-duplicates initialization calls. - * @internal - */ - _initializePromise?: Promise; - - settings: RemoteConfigSettings = { - fetchTimeoutMillis: DEFAULT_FETCH_TIMEOUT_MILLIS, - minimumFetchIntervalMillis: DEFAULT_CACHE_MAX_AGE_MILLIS - }; - - defaultConfig: { [key: string]: string | number | boolean } = {}; - - get fetchTimeMillis(): number { - return this._storageCache.getLastSuccessfulFetchTimestampMillis() || -1; - } - - get lastFetchStatus(): FetchStatus { - return this._storageCache.getLastFetchStatus() || 'no-fetch-yet'; - } - - constructor( - // Required by FirebaseServiceFactory interface. - readonly app: FirebaseApp, - // JS doesn't support private yet - // (https://github.com/tc39/proposal-class-fields#private-fields), so we hint using an - // underscore prefix. - /** - * @internal - */ - readonly _client: RemoteConfigFetchClient, - /** - * @internal - */ - readonly _storageCache: StorageCache, - /** - * @internal - */ - readonly _storage: Storage, - /** - * @internal - */ - readonly _logger: Logger - ) {} -} diff --git a/packages-exp/remote-config-exp/src/storage/storage.ts b/packages-exp/remote-config-exp/src/storage/storage.ts deleted file mode 100644 index f5f457161b1..00000000000 --- a/packages-exp/remote-config-exp/src/storage/storage.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FetchStatus } from '@firebase/remote-config-types'; -import { - FetchResponse, - FirebaseRemoteConfigObject -} from '../client/remote_config_fetch_client'; -import { ERROR_FACTORY, ErrorCode } from '../errors'; -import { FirebaseError } from '@firebase/util'; - -/** - * Converts an error event associated with a {@link IDBRequest} to a {@link FirebaseError}. - */ -function toFirebaseError(event: Event, errorCode: ErrorCode): FirebaseError { - const originalError = (event.target as IDBRequest).error || undefined; - return ERROR_FACTORY.create(errorCode, { - originalErrorMessage: originalError && originalError.message - }); -} - -/** - * A general-purpose store keyed by app + namespace + {@link - * ProjectNamespaceKeyFieldValue}. - * - *

The Remote Config SDK can be used with multiple app installations, and each app can interact - * with multiple namespaces, so this store uses app (ID + name) and namespace as common parent keys - * for a set of key-value pairs. See {@link Storage#createCompositeKey}. - * - *

Visible for testing. - */ -export const APP_NAMESPACE_STORE = 'app_namespace_store'; - -const DB_NAME = 'firebase_remote_config'; -const DB_VERSION = 1; - -/** - * Encapsulates metadata concerning throttled fetch requests. - */ -export interface ThrottleMetadata { - // The number of times fetch has backed off. Used for resuming backoff after a timeout. - backoffCount: number; - // The Unix timestamp in milliseconds when callers can retry a request. - throttleEndTimeMillis: number; -} - -/** - * Provides type-safety for the "key" field used by {@link APP_NAMESPACE_STORE}. - * - *

This seems like a small price to avoid potentially subtle bugs caused by a typo. - */ -type ProjectNamespaceKeyFieldValue = - | 'active_config' - | 'active_config_etag' - | 'last_fetch_status' - | 'last_successful_fetch_timestamp_millis' - | 'last_successful_fetch_response' - | 'settings' - | 'throttle_metadata'; - -// Visible for testing. -export function openDatabase(): Promise { - return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, DB_VERSION); - request.onerror = event => { - reject(toFirebaseError(event, ErrorCode.STORAGE_OPEN)); - }; - request.onsuccess = event => { - resolve((event.target as IDBOpenDBRequest).result); - }; - request.onupgradeneeded = event => { - const db = (event.target as IDBOpenDBRequest).result; - - // We don't use 'break' in this switch statement, the fall-through - // behavior is what we want, because if there are multiple versions between - // the old version and the current version, we want ALL the migrations - // that correspond to those versions to run, not only the last one. - // eslint-disable-next-line default-case - switch (event.oldVersion) { - case 0: - db.createObjectStore(APP_NAMESPACE_STORE, { - keyPath: 'compositeKey' - }); - } - }; - }); -} - -/** - * Abstracts data persistence. - */ -export class Storage { - /** - * @param appId enables storage segmentation by app (ID + name). - * @param appName enables storage segmentation by app (ID + name). - * @param namespace enables storage segmentation by namespace. - */ - constructor( - private readonly appId: string, - private readonly appName: string, - private readonly namespace: string, - private readonly openDbPromise = openDatabase() - ) {} - - getLastFetchStatus(): Promise { - return this.get('last_fetch_status'); - } - - setLastFetchStatus(status: FetchStatus): Promise { - return this.set('last_fetch_status', status); - } - - // This is comparable to a cache entry timestamp. If we need to expire other data, we could - // consider adding timestamp to all storage records and an optional max age arg to getters. - getLastSuccessfulFetchTimestampMillis(): Promise { - return this.get('last_successful_fetch_timestamp_millis'); - } - - setLastSuccessfulFetchTimestampMillis(timestamp: number): Promise { - return this.set( - 'last_successful_fetch_timestamp_millis', - timestamp - ); - } - - getLastSuccessfulFetchResponse(): Promise { - return this.get('last_successful_fetch_response'); - } - - setLastSuccessfulFetchResponse(response: FetchResponse): Promise { - return this.set('last_successful_fetch_response', response); - } - - getActiveConfig(): Promise { - return this.get('active_config'); - } - - setActiveConfig(config: FirebaseRemoteConfigObject): Promise { - return this.set('active_config', config); - } - - getActiveConfigEtag(): Promise { - return this.get('active_config_etag'); - } - - setActiveConfigEtag(etag: string): Promise { - return this.set('active_config_etag', etag); - } - - getThrottleMetadata(): Promise { - return this.get('throttle_metadata'); - } - - setThrottleMetadata(metadata: ThrottleMetadata): Promise { - return this.set('throttle_metadata', metadata); - } - - deleteThrottleMetadata(): Promise { - return this.delete('throttle_metadata'); - } - - async get(key: ProjectNamespaceKeyFieldValue): Promise { - const db = await this.openDbPromise; - return new Promise((resolve, reject) => { - const transaction = db.transaction([APP_NAMESPACE_STORE], 'readonly'); - const objectStore = transaction.objectStore(APP_NAMESPACE_STORE); - const compositeKey = this.createCompositeKey(key); - try { - const request = objectStore.get(compositeKey); - request.onerror = event => { - reject(toFirebaseError(event, ErrorCode.STORAGE_GET)); - }; - request.onsuccess = event => { - const result = (event.target as IDBRequest).result; - if (result) { - resolve(result.value); - } else { - resolve(undefined); - } - }; - } catch (e) { - reject( - ERROR_FACTORY.create(ErrorCode.STORAGE_GET, { - originalErrorMessage: e && e.message - }) - ); - } - }); - } - - async set(key: ProjectNamespaceKeyFieldValue, value: T): Promise { - const db = await this.openDbPromise; - return new Promise((resolve, reject) => { - const transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite'); - const objectStore = transaction.objectStore(APP_NAMESPACE_STORE); - const compositeKey = this.createCompositeKey(key); - try { - const request = objectStore.put({ - compositeKey, - value - }); - request.onerror = (event: Event) => { - reject(toFirebaseError(event, ErrorCode.STORAGE_SET)); - }; - request.onsuccess = () => { - resolve(); - }; - } catch (e) { - reject( - ERROR_FACTORY.create(ErrorCode.STORAGE_SET, { - originalErrorMessage: e && e.message - }) - ); - } - }); - } - - async delete(key: ProjectNamespaceKeyFieldValue): Promise { - const db = await this.openDbPromise; - return new Promise((resolve, reject) => { - const transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite'); - const objectStore = transaction.objectStore(APP_NAMESPACE_STORE); - const compositeKey = this.createCompositeKey(key); - try { - const request = objectStore.delete(compositeKey); - request.onerror = (event: Event) => { - reject(toFirebaseError(event, ErrorCode.STORAGE_DELETE)); - }; - request.onsuccess = () => { - resolve(); - }; - } catch (e) { - reject( - ERROR_FACTORY.create(ErrorCode.STORAGE_DELETE, { - originalErrorMessage: e && e.message - }) - ); - } - }); - } - - // Facilitates composite key functionality (which is unsupported in IE). - createCompositeKey(key: ProjectNamespaceKeyFieldValue): string { - return [this.appId, this.appName, this.namespace, key].join(); - } -} diff --git a/packages-exp/remote-config-exp/src/storage/storage_cache.ts b/packages-exp/remote-config-exp/src/storage/storage_cache.ts deleted file mode 100644 index 5ffbdba20c0..00000000000 --- a/packages-exp/remote-config-exp/src/storage/storage_cache.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FetchStatus } from '@firebase/remote-config-types'; -import { FirebaseRemoteConfigObject } from '../client/remote_config_fetch_client'; -import { Storage } from './storage'; - -/** - * A memory cache layer over storage to support the SDK's synchronous read requirements. - */ -export class StorageCache { - constructor(private readonly storage: Storage) {} - - /** - * Memory caches. - */ - private lastFetchStatus?: FetchStatus; - private lastSuccessfulFetchTimestampMillis?: number; - private activeConfig?: FirebaseRemoteConfigObject; - - /** - * Memory-only getters - */ - getLastFetchStatus(): FetchStatus | undefined { - return this.lastFetchStatus; - } - - getLastSuccessfulFetchTimestampMillis(): number | undefined { - return this.lastSuccessfulFetchTimestampMillis; - } - - getActiveConfig(): FirebaseRemoteConfigObject | undefined { - return this.activeConfig; - } - - /** - * Read-ahead getter - */ - async loadFromStorage(): Promise { - const lastFetchStatusPromise = this.storage.getLastFetchStatus(); - const lastSuccessfulFetchTimestampMillisPromise = this.storage.getLastSuccessfulFetchTimestampMillis(); - const activeConfigPromise = this.storage.getActiveConfig(); - - // Note: - // 1. we consistently check for undefined to avoid clobbering defined values - // in memory - // 2. we defer awaiting to improve readability, as opposed to destructuring - // a Promise.all result, for example - - const lastFetchStatus = await lastFetchStatusPromise; - if (lastFetchStatus) { - this.lastFetchStatus = lastFetchStatus; - } - - const lastSuccessfulFetchTimestampMillis = await lastSuccessfulFetchTimestampMillisPromise; - if (lastSuccessfulFetchTimestampMillis) { - this.lastSuccessfulFetchTimestampMillis = lastSuccessfulFetchTimestampMillis; - } - - const activeConfig = await activeConfigPromise; - if (activeConfig) { - this.activeConfig = activeConfig; - } - } - - /** - * Write-through setters - */ - setLastFetchStatus(status: FetchStatus): Promise { - this.lastFetchStatus = status; - return this.storage.setLastFetchStatus(status); - } - - setLastSuccessfulFetchTimestampMillis( - timestampMillis: number - ): Promise { - this.lastSuccessfulFetchTimestampMillis = timestampMillis; - return this.storage.setLastSuccessfulFetchTimestampMillis(timestampMillis); - } - - setActiveConfig(activeConfig: FirebaseRemoteConfigObject): Promise { - this.activeConfig = activeConfig; - return this.storage.setActiveConfig(activeConfig); - } -} diff --git a/packages-exp/remote-config-exp/src/value.ts b/packages-exp/remote-config-exp/src/value.ts deleted file mode 100644 index f3fb6ff9581..00000000000 --- a/packages-exp/remote-config-exp/src/value.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Value as ValueType, ValueSource } from '@firebase/remote-config-types'; - -const DEFAULT_VALUE_FOR_BOOLEAN = false; -const DEFAULT_VALUE_FOR_STRING = ''; -const DEFAULT_VALUE_FOR_NUMBER = 0; - -const BOOLEAN_TRUTHY_VALUES = ['1', 'true', 't', 'yes', 'y', 'on']; - -export class Value implements ValueType { - constructor( - private readonly _source: ValueSource, - private readonly _value: string = DEFAULT_VALUE_FOR_STRING - ) {} - - asString(): string { - return this._value; - } - - asBoolean(): boolean { - if (this._source === 'static') { - return DEFAULT_VALUE_FOR_BOOLEAN; - } - return BOOLEAN_TRUTHY_VALUES.indexOf(this._value.toLowerCase()) >= 0; - } - - asNumber(): number { - if (this._source === 'static') { - return DEFAULT_VALUE_FOR_NUMBER; - } - let num = Number(this._value); - if (isNaN(num)) { - num = DEFAULT_VALUE_FOR_NUMBER; - } - return num; - } - - getSource(): ValueSource { - return this._source; - } -} diff --git a/packages-exp/remote-config-exp/test/client/caching_client.test.ts b/packages-exp/remote-config-exp/test/client/caching_client.test.ts deleted file mode 100644 index a808dffb605..00000000000 --- a/packages-exp/remote-config-exp/test/client/caching_client.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../setup'; -import { expect } from 'chai'; -import { - RemoteConfigFetchClient, - FetchResponse, - FetchRequest, - RemoteConfigAbortSignal -} from '../../src/client/remote_config_fetch_client'; -import * as sinon from 'sinon'; -import { CachingClient } from '../../src/client/caching_client'; -import { StorageCache } from '../../src/storage/storage_cache'; -import { Storage } from '../../src/storage/storage'; -import { Logger } from '@firebase/logger'; - -const DEFAULT_REQUEST: FetchRequest = { - // Invalidates cache by default. - cacheMaxAgeMillis: 0, - signal: new RemoteConfigAbortSignal() -}; - -describe('CachingClient', () => { - const backingClient = {} as RemoteConfigFetchClient; - const storageCache = {} as StorageCache; - const logger = {} as Logger; - const storage = {} as Storage; - let cachingClient: CachingClient; - let clock: sinon.SinonFakeTimers; - - beforeEach(() => { - logger.debug = sinon.stub(); - cachingClient = new CachingClient( - backingClient, - storage, - storageCache, - logger - ); - clock = sinon.useFakeTimers({ now: 3000 }); // Mocks Date.now as 3000. - }); - - afterEach(() => { - clock.restore(); - }); - - describe('isCacheDataFresh', () => { - it('returns false if cached response is older than max age', () => { - expect( - cachingClient.isCachedDataFresh( - // Mocks a cache set when Date.now was 1000, ie it's two seconds old. - 1000, - // Tolerates a cache one second old. - 1000 - ) - ).to.be.false; - }); - - it('returns true if cached response is equal to max age', () => { - expect(cachingClient.isCachedDataFresh(2000, 1000)).to.be.true; - }); - - it('returns true if cached response is younger than max age', () => { - expect(cachingClient.isCachedDataFresh(3000, 1000)).to.be.true; - }); - }); - - describe('fetch', () => { - beforeEach(() => { - storage.getLastSuccessfulFetchTimestampMillis = sinon - .stub() - .returns(1000); // Mocks a cache set when Date.now was 1000, ie it's two seconds old. - storageCache.setLastSuccessfulFetchTimestampMillis = sinon.stub(); - storage.getLastSuccessfulFetchResponse = sinon.stub(); - storage.setLastSuccessfulFetchResponse = sinon.stub(); - backingClient.fetch = sinon.stub().returns(Promise.resolve({})); - }); - - it('exits early on cache hit', async () => { - const expectedResponse = { config: { eTag: 'etag', color: 'taupe' } }; - storage.getLastSuccessfulFetchResponse = sinon - .stub() - .returns(expectedResponse); - - const actualResponse = await cachingClient.fetch({ - cacheMaxAgeMillis: 2000, - signal: new RemoteConfigAbortSignal() - }); - - expect(actualResponse).to.deep.eq(expectedResponse); - expect(backingClient.fetch).not.to.have.been.called; - }); - - it('fetches on cache miss', async () => { - await cachingClient.fetch(DEFAULT_REQUEST); - - expect(backingClient.fetch).to.have.been.called; - }); - - it('passes etag from last successful fetch', async () => { - const lastSuccessfulFetchResponse = { eTag: 'etag' } as FetchResponse; - storage.getLastSuccessfulFetchResponse = sinon - .stub() - .returns(lastSuccessfulFetchResponse); - - await cachingClient.fetch(DEFAULT_REQUEST); - - expect(backingClient.fetch).to.have.been.calledWith( - Object.assign({}, DEFAULT_REQUEST, { - eTag: lastSuccessfulFetchResponse.eTag - }) - ); - }); - - it('caches timestamp and response if status is 200', async () => { - const response = { - status: 200, - eTag: 'etag', - config: { color: 'clear' } - }; - backingClient.fetch = sinon.stub().returns(Promise.resolve(response)); - - await cachingClient.fetch(DEFAULT_REQUEST); - - expect( - storageCache.setLastSuccessfulFetchTimestampMillis - ).to.have.been.calledWith(3000); // Based on mock timer in beforeEach. - expect(storage.setLastSuccessfulFetchResponse).to.have.been.calledWith( - response - ); - }); - - it('sets timestamp, but not config, if 304', async () => { - backingClient.fetch = sinon - .stub() - .returns(Promise.resolve({ status: 304 })); - - await cachingClient.fetch(DEFAULT_REQUEST); - - expect( - storageCache.setLastSuccessfulFetchTimestampMillis - ).to.have.been.calledWith(3000); // Based on mock timer in beforeEach. - expect(storage.setLastSuccessfulFetchResponse).not.to.have.been.called; - }); - }); -}); diff --git a/packages-exp/remote-config-exp/test/client/rest_client.test.ts b/packages-exp/remote-config-exp/test/client/rest_client.test.ts deleted file mode 100644 index 89b745dacca..00000000000 --- a/packages-exp/remote-config-exp/test/client/rest_client.test.ts +++ /dev/null @@ -1,270 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../setup'; -import { expect } from 'chai'; -import { RestClient } from '../../src/client/rest_client'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import * as sinon from 'sinon'; -import { ERROR_FACTORY, ErrorCode } from '../../src/errors'; -import { FirebaseError } from '@firebase/util'; -import { - FetchRequest, - RemoteConfigAbortSignal -} from '../../src/client/remote_config_fetch_client'; - -const DEFAULT_REQUEST: FetchRequest = { - cacheMaxAgeMillis: 1, - signal: new RemoteConfigAbortSignal() -}; - -describe('RestClient', () => { - const firebaseInstallations = {} as FirebaseInstallations; - let client: RestClient; - - beforeEach(() => { - client = new RestClient( - firebaseInstallations, - 'sdk-version', - 'namespace', - 'project-id', - 'api-key', - 'app-id' - ); - firebaseInstallations.getId = sinon - .stub() - .returns(Promise.resolve('fis-id')); - firebaseInstallations.getToken = sinon - .stub() - .returns(Promise.resolve('fis-token')); - }); - - describe('fetch', () => { - let fetchStub: sinon.SinonStub< - [RequestInfo, RequestInit?], - Promise - >; - - beforeEach(() => { - fetchStub = sinon - .stub(window, 'fetch') - .returns(Promise.resolve(new Response('{}'))); - }); - - afterEach(() => { - fetchStub.restore(); - }); - - it('handles 200/UPDATE responses', async () => { - const expectedResponse = { - status: 200, - eTag: 'etag', - state: 'UPDATE', - entries: { color: 'sparkling' } - }; - - fetchStub.returns( - Promise.resolve({ - ok: true, - status: expectedResponse.status, - headers: new Headers({ ETag: expectedResponse.eTag }), - json: () => - Promise.resolve({ - entries: expectedResponse.entries, - state: expectedResponse.state - }) - } as Response) - ); - - const response = await client.fetch(DEFAULT_REQUEST); - - expect(response).to.deep.eq({ - status: expectedResponse.status, - eTag: expectedResponse.eTag, - config: expectedResponse.entries - }); - }); - - it('calls the correct endpoint', async () => { - await client.fetch(DEFAULT_REQUEST); - - expect(fetchStub).to.be.calledWith( - 'https://firebaseremoteconfig.googleapis.com/v1/projects/project-id/namespaces/namespace:fetch?key=api-key', - sinon.match.object - ); - }); - - it('passes injected params', async () => { - await client.fetch(DEFAULT_REQUEST); - - expect(fetchStub).to.be.calledWith( - sinon.match.string, - sinon.match({ - body: - '{"sdk_version":"sdk-version","app_instance_id":"fis-id","app_instance_id_token":"fis-token","app_id":"app-id","language_code":"en-US"}' - }) - ); - }); - - it('throws on network failure', async () => { - // The Fetch API throws a TypeError on network falure: - // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Exceptions - const originalError = new TypeError('Network request failed'); - fetchStub.returns(Promise.reject(originalError)); - - const fetchPromise = client.fetch(DEFAULT_REQUEST); - - const firebaseError = ERROR_FACTORY.create(ErrorCode.FETCH_NETWORK, { - originalErrorMessage: originalError.message - }); - - await expect(fetchPromise) - .to.eventually.be.rejectedWith(FirebaseError, firebaseError.message) - .with.nested.property( - 'customData.originalErrorMessage', - 'Network request failed' - ); - }); - - it('throws on JSON parse failure', async () => { - // JSON parsing throws a SyntaxError on failure: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Exceptions - const res = new Response(/* empty body */); - sinon - .stub(res, 'json') - .throws(new SyntaxError('Unexpected end of input')); - fetchStub.returns(Promise.resolve(res)); - - const fetchPromise = client.fetch(DEFAULT_REQUEST); - - const firebaseError = ERROR_FACTORY.create(ErrorCode.FETCH_PARSE, { - originalErrorMessage: 'Unexpected end of input' - }); - - await expect(fetchPromise) - .to.eventually.be.rejectedWith(FirebaseError, firebaseError.message) - .with.nested.property( - 'customData.originalErrorMessage', - 'Unexpected end of input' - ); - }); - - it('handles 304 status code and empty body', async () => { - fetchStub.returns( - Promise.resolve({ - status: 304, - headers: new Headers({ ETag: 'response-etag' }) - } as Response) - ); - - const response = await client.fetch( - Object.assign({}, DEFAULT_REQUEST, { - eTag: 'request-etag' - }) - ); - - expect(fetchStub).to.be.calledWith( - sinon.match.string, - sinon.match({ headers: { 'If-None-Match': 'request-etag' } }) - ); - - expect(response).to.deep.eq({ - status: 304, - eTag: 'response-etag', - config: undefined - }); - }); - - it('normalizes INSTANCE_STATE_UNSPECIFIED state to server error', async () => { - fetchStub.returns( - Promise.resolve({ - status: 200, - headers: new Headers({ ETag: 'etag' }), - json: async () => ({ state: 'INSTANCE_STATE_UNSPECIFIED' }) - } as Response) - ); - - const fetchPromise = client.fetch(DEFAULT_REQUEST); - - const error = ERROR_FACTORY.create(ErrorCode.FETCH_STATUS, { - httpStatus: 500 - }); - - await expect(fetchPromise) - .to.eventually.be.rejectedWith(FirebaseError, error.message) - .with.nested.property('customData.httpStatus', 500); - }); - - it('normalizes NO_CHANGE state to 304 status', async () => { - fetchStub.returns( - Promise.resolve({ - status: 200, - headers: new Headers({ ETag: 'etag' }), - json: async () => ({ state: 'NO_CHANGE' }) - } as Response) - ); - - const response = await client.fetch(DEFAULT_REQUEST); - - expect(response).to.deep.eq({ - status: 304, - eTag: 'etag', - config: undefined - }); - }); - - it('normalizes empty change states', async () => { - for (const state of ['NO_TEMPLATE', 'EMPTY_CONFIG']) { - fetchStub.returns( - Promise.resolve({ - status: 200, - headers: new Headers({ ETag: 'etag' }), - json: async () => ({ state }) - } as Response) - ); - - await expect(client.fetch(DEFAULT_REQUEST)).to.eventually.be.deep.eq({ - status: 200, - eTag: 'etag', - config: {} - }); - } - }); - - it('throws error on HTTP error status', async () => { - // Error codes from logs plus an arbitrary unexpected code (300) - for (const status of [300, 400, 403, 404, 415, 429, 500, 503, 504]) { - fetchStub.returns( - Promise.resolve({ - status, - headers: new Headers() - } as Response) - ); - - const fetchPromise = client.fetch(DEFAULT_REQUEST); - - const error = ERROR_FACTORY.create(ErrorCode.FETCH_STATUS, { - httpStatus: status - }); - - await expect(fetchPromise) - .to.eventually.be.rejectedWith(FirebaseError, error.message) - .with.nested.property('customData.httpStatus', status); - } - }); - }); -}); diff --git a/packages-exp/remote-config-exp/test/client/retrying_client.test.ts b/packages-exp/remote-config-exp/test/client/retrying_client.test.ts deleted file mode 100644 index 65641b438bd..00000000000 --- a/packages-exp/remote-config-exp/test/client/retrying_client.test.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import { Storage, ThrottleMetadata } from '../../src/storage/storage'; -import { - RemoteConfigFetchClient, - FetchRequest, - FetchResponse, - RemoteConfigAbortSignal -} from '../../src/client/remote_config_fetch_client'; -import { - setAbortableTimeout, - RetryingClient -} from '../../src/client/retrying_client'; -import { ErrorCode, ERROR_FACTORY } from '../../src/errors'; -import '../setup'; - -const DEFAULT_REQUEST: FetchRequest = { - cacheMaxAgeMillis: 1, - signal: new RemoteConfigAbortSignal() -}; - -describe('RetryingClient', () => { - let backingClient: RemoteConfigFetchClient; - let storage: Storage; - let retryingClient: RetryingClient; - let abortSignal: RemoteConfigAbortSignal; - - beforeEach(() => { - backingClient = {} as RemoteConfigFetchClient; - storage = {} as Storage; - retryingClient = new RetryingClient(backingClient, storage); - storage.getThrottleMetadata = sinon.stub().returns(Promise.resolve()); - storage.deleteThrottleMetadata = sinon.stub().returns(Promise.resolve()); - storage.setThrottleMetadata = sinon.stub().returns(Promise.resolve()); - backingClient.fetch = sinon - .stub() - .returns(Promise.resolve({ status: 200 })); - abortSignal = new RemoteConfigAbortSignal(); - }); - - describe('setAbortableTimeout', () => { - let clock: sinon.SinonFakeTimers; - - beforeEach(() => { - // Sets Date.now() to zero. - clock = sinon.useFakeTimers(); - }); - - afterEach(() => { - clock.restore(); - }); - - it('Derives backoff from end time', async () => { - const setTimeoutSpy = sinon.spy(window, 'setTimeout'); - - const timeoutPromise = setAbortableTimeout(abortSignal, Date.now() + 1); - - // Advances mocked clock so setTimeout logic runs. - clock.runAll(); - - await timeoutPromise; - - expect(setTimeoutSpy).to.have.been.calledWith(sinon.match.any, 1); - }); - - it('Normalizes end time in the past to zero backoff', async () => { - const setTimeoutSpy = sinon.spy(window, 'setTimeout'); - - const timeoutPromise = setAbortableTimeout(abortSignal, Date.now() - 1); - - // Advances mocked clock so setTimeout logic runs. - clock.runAll(); - - await timeoutPromise; - - expect(setTimeoutSpy).to.have.been.calledWith(sinon.match.any, 0); - - setTimeoutSpy.restore(); - }); - - it('listens for abort event and rejects promise', async () => { - const throttleEndTimeMillis = 1000; - - const timeoutPromise = setAbortableTimeout( - abortSignal, - throttleEndTimeMillis - ); - - abortSignal.abort(); - - const expectedError = ERROR_FACTORY.create(ErrorCode.FETCH_THROTTLE, { - throttleEndTimeMillis - }); - - await expect(timeoutPromise).to.eventually.be.rejectedWith( - expectedError.message - ); - }); - }); - - describe('fetch', () => { - it('returns success response', async () => { - const setTimeoutSpy = sinon.spy(window, 'setTimeout'); - - const expectedResponse: FetchResponse = { - status: 200, - eTag: 'etag', - config: {} - }; - backingClient.fetch = sinon - .stub() - .returns(Promise.resolve(expectedResponse)); - - const actualResponse = retryingClient.fetch(DEFAULT_REQUEST); - - await expect(actualResponse).to.eventually.deep.eq(expectedResponse); - - // Asserts setTimeout is passed a zero delay, since throttleEndTimeMillis is set to Date.now, - // which is faked to be a constant. - expect(setTimeoutSpy).to.have.been.calledWith(sinon.match.any, 0); - - expect(storage.deleteThrottleMetadata).to.have.been.called; - - setTimeoutSpy.restore(); - }); - - it('rethrows unretriable errors rather than retrying', async () => { - const expectedError = ERROR_FACTORY.create(ErrorCode.FETCH_STATUS, { - httpStatus: 400 - }); - backingClient.fetch = sinon.stub().returns(Promise.reject(expectedError)); - - const fetchPromise = retryingClient.fetch(DEFAULT_REQUEST); - - await expect(fetchPromise).to.eventually.be.rejectedWith(expectedError); - }); - - it('retries on retriable errors', async () => { - // Configures Date.now() to advance clock from zero in 20ms increments, enabling - // tests to assert a known throttle end time and allow setTimeout to work. - const clock = sinon.useFakeTimers({ shouldAdvanceTime: true }); - - // Ensures backoff is always zero, which simplifies reasoning about timer. - const powSpy = sinon.stub(Math, 'pow').returns(0); - const randomSpy = sinon.stub(Math, 'random').returns(0.5); - - // Simulates a service call that returns errors several times before returning success. - // Error codes from logs. - const errorResponseStatuses = [429, 500, 503, 504]; - const errorResponseCount = errorResponseStatuses.length; - - backingClient.fetch = sinon.stub().callsFake(() => { - const httpStatus = errorResponseStatuses.pop(); - - if (httpStatus) { - // Triggers retry by returning a retriable status code. - const expectedError = ERROR_FACTORY.create(ErrorCode.FETCH_STATUS, { - httpStatus - }); - return Promise.reject(expectedError); - } - - // Halts retrying by returning success. - // Note backoff never terminates if the server always errors. - return Promise.resolve({ status: 200 }); - }); - - await retryingClient.fetch(DEFAULT_REQUEST); - - // Asserts throttle metadata was persisted after each error response. - for (let i = 1; i <= errorResponseCount; i++) { - expect(storage.setThrottleMetadata).to.have.been.calledWith({ - backoffCount: i, - throttleEndTimeMillis: i * 20 - }); - } - - powSpy.restore(); - randomSpy.restore(); - clock.restore(); - }); - }); - - describe('attemptFetch', () => { - it('honors metadata when initializing', async () => { - const clock = sinon.useFakeTimers({ shouldAdvanceTime: true }); - const setTimeoutSpy = sinon.spy(window, 'setTimeout'); - - const throttleMetadata = { - throttleEndTimeMillis: 123 - } as ThrottleMetadata; - - await retryingClient.attemptFetch(DEFAULT_REQUEST, throttleMetadata); - - expect(setTimeoutSpy).to.have.been.calledWith(sinon.match.any, 123); - - clock.restore(); - setTimeoutSpy.restore(); - }); - }); -}); diff --git a/packages-exp/remote-config-exp/test/errors.test.ts b/packages-exp/remote-config-exp/test/errors.test.ts deleted file mode 100644 index 67d41bfa505..00000000000 --- a/packages-exp/remote-config-exp/test/errors.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { hasErrorCode, ERROR_FACTORY, ErrorCode } from '../src/errors'; -import './setup'; - -describe('hasErrorCode', () => { - it('defaults false', () => { - const error = new Error(); - expect(hasErrorCode(error, ErrorCode.REGISTRATION_PROJECT_ID)).to.be.false; - }); - it('returns true for FirebaseError with given code', () => { - const error = ERROR_FACTORY.create(ErrorCode.REGISTRATION_PROJECT_ID); - expect(hasErrorCode(error, ErrorCode.REGISTRATION_PROJECT_ID)).to.be.true; - }); -}); diff --git a/packages-exp/remote-config-exp/test/language.test.ts b/packages-exp/remote-config-exp/test/language.test.ts deleted file mode 100644 index a92630467d4..00000000000 --- a/packages-exp/remote-config-exp/test/language.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { getUserLanguage } from '../src/language'; -import './setup'; - -// Adapts getUserLanguage tests from packages/auth/test/utils_test.js for TypeScript. -describe('getUserLanguage', () => { - it('prioritizes navigator.languages', () => { - expect( - getUserLanguage({ - languages: ['de', 'en'], - language: 'en' - }) - ).to.eq('de'); - }); - - it('falls back to navigator.language', () => { - expect( - getUserLanguage({ - language: 'en' - } as NavigatorLanguage) - ).to.eq('en'); - }); - - it('defaults undefined', () => { - expect(getUserLanguage({} as NavigatorLanguage)).to.be.undefined; - }); -}); diff --git a/packages-exp/remote-config-exp/test/remote_config.test.ts b/packages-exp/remote-config-exp/test/remote_config.test.ts deleted file mode 100644 index cb1f6066562..00000000000 --- a/packages-exp/remote-config-exp/test/remote_config.test.ts +++ /dev/null @@ -1,520 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp } from '@firebase/app-exp'; -import { - RemoteConfig as RemoteConfigType, - LogLevel as RemoteConfigLogLevel -} from '../src/public_types'; -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import { StorageCache } from '../src/storage/storage_cache'; -import { Storage } from '../src/storage/storage'; -import { RemoteConfig } from '../src/remote_config'; -import { - RemoteConfigFetchClient, - FetchResponse -} from '../src/client/remote_config_fetch_client'; -import { Value } from '../src/value'; -import './setup'; -import { ERROR_FACTORY, ErrorCode } from '../src/errors'; -import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger'; -import { - activate, - ensureInitialized, - getAll, - getBoolean, - getNumber, - getString, - getValue, - setLogLevel, - fetchConfig -} from '../src/api'; -import * as api from '../src/api'; -import { fetchAndActivate } from '../src'; -import { restore } from 'sinon'; - -describe('RemoteConfig', () => { - const ACTIVE_CONFIG = { - key1: 'active_config_value_1', - key2: 'active_config_value_2', - key3: 'true', - key4: '123' - }; - const DEFAULT_CONFIG = { - key1: 'default_config_value_1', - key2: 'default_config_value_2', - key3: 'false', - key4: '345', - test: 'test' - }; - - let app: FirebaseApp; - let client: RemoteConfigFetchClient; - let storageCache: StorageCache; - let storage: Storage; - let logger: Logger; - let rc: RemoteConfigType; - - let getActiveConfigStub: sinon.SinonStub; - let loggerDebugSpy: sinon.SinonSpy; - let loggerLogLevelSpy: any; - - beforeEach(() => { - // Clears stubbed behavior between each test. - app = {} as FirebaseApp; - client = {} as RemoteConfigFetchClient; - storageCache = {} as StorageCache; - storage = {} as Storage; - logger = new Logger('package-name'); - getActiveConfigStub = sinon.stub().returns(undefined); - storageCache.getActiveConfig = getActiveConfigStub; - loggerDebugSpy = sinon.spy(logger, 'debug'); - loggerLogLevelSpy = sinon.spy(logger, 'logLevel', ['set']); - rc = new RemoteConfig(app, client, storageCache, storage, logger); - }); - - afterEach(() => { - loggerDebugSpy.restore(); - loggerLogLevelSpy.restore(); - }); - - // Adapts getUserLanguage tests from packages/auth/test/utils_test.js for TypeScript. - describe('setLogLevel', () => { - it('proxies to the FirebaseLogger instance', () => { - setLogLevel(rc, 'debug'); - - // Casts spy to any because property setters aren't defined on the SinonSpy type. - expect(loggerLogLevelSpy.set).to.have.been.calledWith( - FirebaseLogLevel.DEBUG - ); - }); - - it('normalizes levels other than DEBUG and SILENT to ERROR', () => { - for (const logLevel of ['info', 'verbose', 'error', 'severe']) { - setLogLevel(rc, logLevel as RemoteConfigLogLevel); - - // Casts spy to any because property setters aren't defined on the SinonSpy type. - expect(loggerLogLevelSpy.set).to.have.been.calledWith( - FirebaseLogLevel.ERROR - ); - } - }); - }); - - describe('ensureInitialized', () => { - it('warms cache', async () => { - storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve()); - - await ensureInitialized(rc); - - expect(storageCache.loadFromStorage).to.have.been.calledOnce; - }); - - it('de-duplicates repeated calls', async () => { - storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve()); - - await ensureInitialized(rc); - await ensureInitialized(rc); - - expect(storageCache.loadFromStorage).to.have.been.calledOnce; - }); - }); - - describe('fetchTimeMillis', () => { - it('normalizes undefined values', async () => { - storageCache.getLastSuccessfulFetchTimestampMillis = sinon - .stub() - .returns(undefined); - - expect(rc.fetchTimeMillis).to.eq(-1); - }); - - it('reads from cache', async () => { - const lastFetchTimeMillis = 123; - - storageCache.getLastSuccessfulFetchTimestampMillis = sinon - .stub() - .returns(lastFetchTimeMillis); - - expect(rc.fetchTimeMillis).to.eq(lastFetchTimeMillis); - }); - }); - - describe('lastFetchStatus', () => { - it('normalizes undefined values', async () => { - storageCache.getLastFetchStatus = sinon.stub().returns(undefined); - - expect(rc.lastFetchStatus).to.eq('no-fetch-yet'); - }); - - it('reads from cache', async () => { - const lastFetchStatus = 'success'; - - storageCache.getLastFetchStatus = sinon.stub().returns(lastFetchStatus); - - expect(rc.lastFetchStatus).to.eq(lastFetchStatus); - }); - }); - - describe('getValue', () => { - it('returns the active value if available', () => { - getActiveConfigStub.returns(ACTIVE_CONFIG); - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getValue(rc, 'key1')).to.deep.eq( - new Value('remote', ACTIVE_CONFIG.key1) - ); - }); - - it('returns the default value if active is not available', () => { - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getValue(rc, 'key1')).to.deep.eq( - new Value('default', DEFAULT_CONFIG.key1) - ); - }); - - it('returns the stringified default boolean values if active is not available', () => { - const DEFAULTS = { trueVal: true, falseVal: false }; - rc.defaultConfig = DEFAULTS; - - expect(getValue(rc, 'trueVal')).to.deep.eq( - new Value('default', String(DEFAULTS.trueVal)) - ); - expect(getValue(rc, 'falseVal')).to.deep.eq( - new Value('default', String(DEFAULTS.falseVal)) - ); - }); - - it('returns the stringified default numeric values if active is not available', () => { - const DEFAULTS = { negative: -1, zero: 0, positive: 11 }; - rc.defaultConfig = DEFAULTS; - - expect(getValue(rc, 'negative')).to.deep.eq( - new Value('default', String(DEFAULTS.negative)) - ); - expect(getValue(rc, 'zero')).to.deep.eq( - new Value('default', String(DEFAULTS.zero)) - ); - expect(getValue(rc, 'positive')).to.deep.eq( - new Value('default', String(DEFAULTS.positive)) - ); - }); - - it('returns the static value if active and default are not available', () => { - expect(getValue(rc, 'key1')).to.deep.eq(new Value('static')); - - // Asserts debug message logged if static value is returned, per EAP feedback. - expect(logger.debug).to.have.been.called; - }); - - it('logs if initialization is incomplete', async () => { - // Defines default value to isolate initialization logging from static value logging. - rc.defaultConfig = { key1: 'val' }; - - // Gets value before initialization. - getValue(rc, 'key1'); - - // Asserts getValue logs. - expect(logger.debug).to.have.been.called; - - // Enables initialization to complete. - storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve()); - - // Ensures initialization completes. - await ensureInitialized(rc); - - // Gets value after initialization. - getValue(rc, 'key1'); - - // Asserts getValue doesn't log after initialization is complete. - expect(logger.debug).to.have.been.calledOnce; - }); - }); - - describe('getBoolean', () => { - it('returns the active value if available', () => { - getActiveConfigStub.returns(ACTIVE_CONFIG); - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getBoolean(rc, 'key3')).to.be.true; - }); - - it('returns the default value if active is not available', () => { - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getBoolean(rc, 'key3')).to.be.false; - }); - - it('returns the static value if active and default are not available', () => { - expect(getBoolean(rc, 'key3')).to.be.false; - }); - }); - - describe('getString', () => { - it('returns the active value if available', () => { - getActiveConfigStub.returns(ACTIVE_CONFIG); - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getString(rc, 'key1')).to.eq(ACTIVE_CONFIG.key1); - }); - - it('returns the default value if active is not available', () => { - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getString(rc, 'key2')).to.eq(DEFAULT_CONFIG.key2); - }); - - it('returns the static value if active and default are not available', () => { - expect(getString(rc, 'key1')).to.eq(''); - }); - }); - - describe('getNumber', () => { - it('returns the active value if available', () => { - getActiveConfigStub.returns(ACTIVE_CONFIG); - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getNumber(rc, 'key4')).to.eq(Number(ACTIVE_CONFIG.key4)); - }); - - it('returns the default value if active is not available', () => { - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getNumber(rc, 'key4')).to.eq(Number(DEFAULT_CONFIG.key4)); - }); - - it('returns the static value if active and default are not available', () => { - expect(getNumber(rc, 'key1')).to.eq(0); - }); - }); - - describe('getAll', () => { - it('returns values for all keys included in active and default configs', () => { - getActiveConfigStub.returns(ACTIVE_CONFIG); - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getAll(rc)).to.deep.eq({ - key1: new Value('remote', ACTIVE_CONFIG.key1), - key2: new Value('remote', ACTIVE_CONFIG.key2), - key3: new Value('remote', ACTIVE_CONFIG.key3), - key4: new Value('remote', ACTIVE_CONFIG.key4), - test: new Value('default', DEFAULT_CONFIG.test) - }); - }); - - it('returns values in default if active is not available', () => { - rc.defaultConfig = DEFAULT_CONFIG; - - expect(getAll(rc)).to.deep.eq({ - key1: new Value('default', DEFAULT_CONFIG.key1), - key2: new Value('default', DEFAULT_CONFIG.key2), - key3: new Value('default', DEFAULT_CONFIG.key3), - key4: new Value('default', DEFAULT_CONFIG.key4), - test: new Value('default', DEFAULT_CONFIG.test) - }); - }); - - it('returns empty object if both active and default configs are not defined', () => { - expect(getAll(rc)).to.deep.eq({}); - }); - }); - - describe('activate', () => { - const ETAG = 'etag'; - const CONFIG = { key: 'val' }; - const NEW_ETAG = 'new_etag'; - - let getLastSuccessfulFetchResponseStub: sinon.SinonStub; - let getActiveConfigEtagStub: sinon.SinonStub; - let setActiveConfigEtagStub: sinon.SinonStub; - let setActiveConfigStub: sinon.SinonStub; - - beforeEach(() => { - getLastSuccessfulFetchResponseStub = sinon.stub(); - getActiveConfigEtagStub = sinon.stub(); - setActiveConfigEtagStub = sinon.stub(); - setActiveConfigStub = sinon.stub(); - - storage.getLastSuccessfulFetchResponse = getLastSuccessfulFetchResponseStub; - storage.getActiveConfigEtag = getActiveConfigEtagStub; - storage.setActiveConfigEtag = setActiveConfigEtagStub; - storageCache.setActiveConfig = setActiveConfigStub; - }); - - it('does not activate if last successful fetch response is undefined', async () => { - getLastSuccessfulFetchResponseStub.returns(Promise.resolve()); - getActiveConfigEtagStub.returns(Promise.resolve(ETAG)); - - const activateResponse = await activate(rc); - - expect(activateResponse).to.be.false; - expect(storage.setActiveConfigEtag).to.not.have.been.called; - expect(storageCache.setActiveConfig).to.not.have.been.called; - }); - - it('does not activate if fetched and active etags are the same', async () => { - getLastSuccessfulFetchResponseStub.returns( - Promise.resolve({ config: {}, etag: ETAG }) - ); - getActiveConfigEtagStub.returns(Promise.resolve(ETAG)); - - const activateResponse = await activate(rc); - - expect(activateResponse).to.be.false; - expect(storage.setActiveConfigEtag).to.not.have.been.called; - expect(storageCache.setActiveConfig).to.not.have.been.called; - }); - - it('activates if fetched and active etags are different', async () => { - getLastSuccessfulFetchResponseStub.returns( - Promise.resolve({ config: CONFIG, eTag: NEW_ETAG }) - ); - getActiveConfigEtagStub.returns(Promise.resolve(ETAG)); - - const activateResponse = await activate(rc); - - expect(activateResponse).to.be.true; - expect(storage.setActiveConfigEtag).to.have.been.calledWith(NEW_ETAG); - expect(storageCache.setActiveConfig).to.have.been.calledWith(CONFIG); - }); - - it('activates if fetched is defined but active config is not', async () => { - getLastSuccessfulFetchResponseStub.returns( - Promise.resolve({ config: CONFIG, eTag: NEW_ETAG }) - ); - getActiveConfigEtagStub.returns(Promise.resolve()); - - const activateResponse = await activate(rc); - - expect(activateResponse).to.be.true; - expect(storage.setActiveConfigEtag).to.have.been.calledWith(NEW_ETAG); - expect(storageCache.setActiveConfig).to.have.been.calledWith(CONFIG); - }); - }); - - describe('fetchAndActivate', () => { - let rcActivateStub: sinon.SinonStub<[RemoteConfigType], Promise>; - - beforeEach(() => { - sinon.stub(api, 'fetchConfig').returns(Promise.resolve()); - rcActivateStub = sinon.stub(api, 'activate'); - }); - - afterEach(() => restore()); - - it('calls fetch and activate and returns activation boolean if true', async () => { - rcActivateStub.returns(Promise.resolve(true)); - - const response = await fetchAndActivate(rc); - - expect(response).to.be.true; - expect(api.fetchConfig).to.have.been.calledWith(rc); - expect(api.activate).to.have.been.calledWith(rc); - }); - - it('calls fetch and activate and returns activation boolean if false', async () => { - rcActivateStub.returns(Promise.resolve(false)); - - const response = await fetchAndActivate(rc); - - expect(response).to.be.false; - expect(api.fetchConfig).to.have.been.calledWith(rc); - expect(api.activate).to.have.been.calledWith(rc); - }); - }); - - describe('fetch', () => { - let timeoutStub: sinon.SinonStub< - [(...args: any[]) => void, number, ...any[]] - >; - beforeEach(() => { - client.fetch = sinon - .stub() - .returns(Promise.resolve({ status: 200 } as FetchResponse)); - storageCache.setLastFetchStatus = sinon.stub(); - timeoutStub = sinon.stub(window, 'setTimeout'); - }); - - afterEach(() => { - timeoutStub.restore(); - }); - - it('defines a default timeout', async () => { - await fetchConfig(rc); - - expect(timeoutStub).to.have.been.calledWith(sinon.match.any, 60000); - }); - - it('honors a custom timeout', async () => { - rc.settings.fetchTimeoutMillis = 1000; - - await fetchConfig(rc); - - expect(timeoutStub).to.have.been.calledWith(sinon.match.any, 1000); - }); - - it('sets success status', async () => { - for (const status of [200, 304]) { - client.fetch = sinon - .stub() - .returns(Promise.resolve({ status } as FetchResponse)); - - await fetchConfig(rc); - - expect(storageCache.setLastFetchStatus).to.have.been.calledWith( - 'success' - ); - } - }); - - it('sets throttle status', async () => { - storage.getThrottleMetadata = sinon.stub().returns(Promise.resolve({})); - - const error = ERROR_FACTORY.create(ErrorCode.FETCH_THROTTLE, { - throttleEndTimeMillis: 123 - }); - - client.fetch = sinon.stub().returns(Promise.reject(error)); - - const fetchPromise = fetchConfig(rc); - - await expect(fetchPromise).to.eventually.be.rejectedWith(error); - expect(storageCache.setLastFetchStatus).to.have.been.calledWith( - 'throttle' - ); - }); - - it('defaults to failure status', async () => { - storage.getThrottleMetadata = sinon.stub().returns(Promise.resolve()); - - const error = ERROR_FACTORY.create(ErrorCode.FETCH_STATUS, { - httpStatus: 400 - }); - - client.fetch = sinon.stub().returns(Promise.reject(error)); - - const fetchPromise = fetchConfig(rc); - - await expect(fetchPromise).to.eventually.be.rejectedWith(error); - expect(storageCache.setLastFetchStatus).to.have.been.calledWith( - 'failure' - ); - }); - }); -}); diff --git a/packages-exp/remote-config-exp/test/setup.ts b/packages-exp/remote-config-exp/test/setup.ts deleted file mode 100644 index 90d154f1400..00000000000 --- a/packages-exp/remote-config-exp/test/setup.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { use } from 'chai'; -import * as sinonChai from 'sinon-chai'; -import * as chaiAsPromised from 'chai-as-promised'; - -// Normalizes Sinon assertions to Chai syntax. -use(sinonChai); - -// Adds Promise-friendly syntax to Chai. -use(chaiAsPromised); diff --git a/packages-exp/remote-config-exp/test/storage/storage.test.ts b/packages-exp/remote-config-exp/test/storage/storage.test.ts deleted file mode 100644 index 4543b574094..00000000000 --- a/packages-exp/remote-config-exp/test/storage/storage.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../setup'; -import { expect } from 'chai'; -import { - Storage, - ThrottleMetadata, - openDatabase, - APP_NAMESPACE_STORE -} from '../../src/storage/storage'; -import { FetchResponse } from '../../src/client/remote_config_fetch_client'; - -// Clears global IndexedDB state. -async function clearDatabase(): Promise { - const db = await openDatabase(); - db.transaction([APP_NAMESPACE_STORE], 'readwrite') - .objectStore(APP_NAMESPACE_STORE) - .clear(); -} - -describe('Storage', () => { - const storage = new Storage('appId', 'appName', 'namespace'); - - beforeEach(async () => { - await clearDatabase(); - }); - - it('constructs a composite key', async () => { - // This is defensive, but the cost of accidentally changing the key composition is high. - expect(storage.createCompositeKey('throttle_metadata')).to.eq( - 'appId,appName,namespace,throttle_metadata' - ); - }); - - it('sets and gets last fetch attempt status', async () => { - const expectedStatus = 'success'; - - await storage.setLastFetchStatus(expectedStatus); - - const actualStatus = await storage.getLastFetchStatus(); - - expect(actualStatus).to.deep.eq(expectedStatus); - }); - - it('sets and gets last fetch success timestamp', async () => { - const lastSuccessfulFetchTimestampMillis = 123; - - await storage.setLastSuccessfulFetchTimestampMillis( - lastSuccessfulFetchTimestampMillis - ); - - const actualMetadata = await storage.getLastSuccessfulFetchTimestampMillis(); - - expect(actualMetadata).to.deep.eq(lastSuccessfulFetchTimestampMillis); - }); - - it('sets and gets last successful fetch response', async () => { - const lastSuccessfulFetchResponse = { status: 200 } as FetchResponse; - - await storage.setLastSuccessfulFetchResponse(lastSuccessfulFetchResponse); - - const actualConfig = await storage.getLastSuccessfulFetchResponse(); - - expect(actualConfig).to.deep.eq(lastSuccessfulFetchResponse); - }); - - it('sets and gets active config', async () => { - const expectedConfig = { key: 'value' }; - - await storage.setActiveConfig(expectedConfig); - - const storedConfig = await storage.getActiveConfig(); - - expect(storedConfig).to.deep.eq(expectedConfig); - }); - - it('sets and gets active config etag', async () => { - const expectedEtag = 'etag'; - - await storage.setActiveConfigEtag(expectedEtag); - - const storedConfigEtag = await storage.getActiveConfigEtag(); - - expect(storedConfigEtag).to.deep.eq(expectedEtag); - }); - - it('sets, gets and deletes throttle metadata', async () => { - const expectedMetadata = { - throttleEndTimeMillis: 1 - } as ThrottleMetadata; - - await storage.setThrottleMetadata(expectedMetadata); - - let actualMetadata = await storage.getThrottleMetadata(); - - expect(actualMetadata).to.deep.eq(expectedMetadata); - - await storage.deleteThrottleMetadata(); - - actualMetadata = await storage.getThrottleMetadata(); - - expect(actualMetadata).to.be.undefined; - }); -}); diff --git a/packages-exp/remote-config-exp/test/storage/storage_cache.test.ts b/packages-exp/remote-config-exp/test/storage/storage_cache.test.ts deleted file mode 100644 index e7cfb0ef0da..00000000000 --- a/packages-exp/remote-config-exp/test/storage/storage_cache.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../setup'; -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import { Storage } from '../../src/storage/storage'; -import { StorageCache } from '../../src/storage/storage_cache'; - -describe('StorageCache', () => { - const storage = {} as Storage; - let storageCache: StorageCache; - - beforeEach(() => { - storageCache = new StorageCache(storage); - }); - - /** - * Read-ahead getter. - */ - describe('loadFromStorage', () => { - it('populates memory cache with persisted data', async () => { - const status = 'success'; - const lastSuccessfulFetchTimestampMillis = 123; - const activeConfig = { key: 'value' }; - - storage.getLastFetchStatus = sinon - .stub() - .returns(Promise.resolve(status)); - storage.getLastSuccessfulFetchTimestampMillis = sinon - .stub() - .returns(Promise.resolve(lastSuccessfulFetchTimestampMillis)); - storage.getActiveConfig = sinon - .stub() - .returns(Promise.resolve(activeConfig)); - - await storageCache.loadFromStorage(); - - expect(storage.getLastFetchStatus).to.have.been.called; - expect(storage.getLastSuccessfulFetchTimestampMillis).to.have.been.called; - expect(storage.getActiveConfig).to.have.been.called; - - expect(storageCache.getLastFetchStatus()).to.eq(status); - expect(storageCache.getLastSuccessfulFetchTimestampMillis()).to.deep.eq( - lastSuccessfulFetchTimestampMillis - ); - expect(storageCache.getActiveConfig()).to.deep.eq(activeConfig); - }); - }); - - describe('setActiveConfig', () => { - const activeConfig = { key: 'value2' }; - - beforeEach(() => { - storage.setActiveConfig = sinon.stub().returns(Promise.resolve()); - }); - - it('writes to memory cache', async () => { - await storageCache.setActiveConfig(activeConfig); - - expect(storageCache.getActiveConfig()).to.deep.eq(activeConfig); - }); - - it('writes to persistent storage', async () => { - await storageCache.setActiveConfig(activeConfig); - - expect(storage.setActiveConfig).to.have.been.calledWith(activeConfig); - }); - }); -}); diff --git a/packages-exp/remote-config-exp/test/value.test.ts b/packages-exp/remote-config-exp/test/value.test.ts deleted file mode 100644 index ead90ce25cf..00000000000 --- a/packages-exp/remote-config-exp/test/value.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import './setup'; -import { expect } from 'chai'; -import { Value } from '../src/value'; - -describe('Value', () => { - describe('asString', () => { - it('returns static default string if source is static', () => { - expect(new Value('static').asString()).to.eq(''); - }); - - it('returns the value as a string', () => { - const VALUE = 'test'; - const value = new Value('remote', VALUE); - - expect(value.asString()).to.eq(VALUE); - }); - }); - - describe('asBoolean', () => { - it('returns static default boolean if source is static', () => { - expect(new Value('static').asBoolean()).to.be.false; - }); - - it('returns true for a truthy values', () => { - expect(new Value('remote', '1').asBoolean()).to.be.true; - expect(new Value('remote', 'true').asBoolean()).to.be.true; - expect(new Value('remote', 't').asBoolean()).to.be.true; - expect(new Value('remote', 'yes').asBoolean()).to.be.true; - expect(new Value('remote', 'y').asBoolean()).to.be.true; - expect(new Value('remote', 'on').asBoolean()).to.be.true; - }); - - it('returns false for non-truthy values', () => { - expect(new Value('remote', '').asBoolean()).to.be.false; - expect(new Value('remote', 'false').asBoolean()).to.be.false; - expect(new Value('remote', 'random string').asBoolean()).to.be.false; - }); - }); - - describe('asNumber', () => { - it('returns static default number if source is static', () => { - expect(new Value('static').asNumber()).to.eq(0); - }); - - it('returns value as a number', () => { - expect(new Value('default', '33').asNumber()).to.eq(33); - expect(new Value('default', 'not a number').asNumber()).to.eq(0); - expect(new Value('default', '-10').asNumber()).to.eq(-10); - expect(new Value('default', '0').asNumber()).to.eq(0); - expect(new Value('default', '5.3').asNumber()).to.eq(5.3); - }); - }); - - describe('getSource', () => { - it('returns the source of the value', () => { - expect(new Value('default', 'test').getSource()).to.eq('default'); - expect(new Value('remote', 'test').getSource()).to.eq('remote'); - expect(new Value('static').getSource()).to.eq('static'); - }); - }); -}); diff --git a/packages-exp/remote-config-exp/test_app/index.html b/packages-exp/remote-config-exp/test_app/index.html deleted file mode 100644 index effc7bd1492..00000000000 --- a/packages-exp/remote-config-exp/test_app/index.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - Test App - - - - - -

-

Remote Config Test App

-
-
-
-
Firebase config
-
- -
-
-
RC defaults
-
- -
-
-
RC settings
-
- -
-
-
Log level
-
- -
-
-
-
-
Controls
-
- - - - -
-
-
-
Value getters
-
-
- key: - -
- - - - -
-
-
-
General getters
-
- - - - -
-
-
-
-
Output
-
-
-
-
-
-
- - - diff --git a/packages-exp/remote-config-exp/test_app/index.js b/packages-exp/remote-config-exp/test_app/index.js deleted file mode 100644 index 5fe575bbbb4..00000000000 --- a/packages-exp/remote-config-exp/test_app/index.js +++ /dev/null @@ -1,224 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const FB_CONFIG_PLACEHOLDER = `{ - apiKey: ..., - authDomain: ..., - databaseURL: ..., - projectId: ..., - storageBucket: ..., - messagingSenderId: ..., - appId: ... -}`; -const DEFAULTS_PLACEHOLDER = `{ - key1: 'value1', - key2: 'value2', - ... -}`; -const SETTINGS_PLACEHOLDER = `{ - minimumFetchIntervalMillis: 43200000, - fetchTimeoutMillis: 5000 -}`; -const SUCCESS_MESSAGE = 'Done '; - -let rcInstance; -const outputBox = document.getElementById('output-box'); - -window.onload = function () { - document.querySelector( - '#firebase-config' - ).placeholder = FB_CONFIG_PLACEHOLDER; - document.querySelector('#rc-defaults').placeholder = DEFAULTS_PLACEHOLDER; - document.querySelector('#rc-settings').placeholder = SETTINGS_PLACEHOLDER; -}; - -function initializeFirebase() { - const val = document.querySelector('#firebase-config').value; - const app = firebase.app.initializeApp(parseObjFromStr(val)); - rcInstance = firebase.remoteConfig.getRemoteConfig(app); - return Promise.resolve(); -} - -function setDefaults() { - rcInstance.defaultConfig = parseObjFromStr( - document.querySelector('#rc-defaults').value - ); - return SUCCESS_MESSAGE; -} - -function setSettings() { - const newSettings = parseObjFromStr( - document.querySelector('#rc-settings').value, - true - ); - const currentSettings = rcInstance.settings; - rcInstance.settings = Object.assign({}, currentSettings, newSettings); - return SUCCESS_MESSAGE; -} - -function setLogLevel() { - const newLogLevel = document.querySelector('#log-level-input').value; - firebase.remoteConfig.setLogLevel(rcInstance, newLogLevel); - return SUCCESS_MESSAGE; -} - -function activate() { - return firebase.remoteConfig.activate(rcInstance); -} - -function ensureInitialized() { - return firebase.remoteConfig.ensureInitialized(rcInstance); -} - -// Prefixed to avoid clobbering the browser's fetch function. -function rcFetch() { - return firebase.remoteConfig.fetchConfig(rcInstance); -} - -function fetchAndActivate() { - return firebase.remoteConfig.fetchAndActivate(rcInstance); -} - -function getString() { - return firebase.remoteConfig.getString(rcInstance, getKey()); -} - -function getBoolean() { - return firebase.remoteConfig.getBoolean(rcInstance, getKey()); -} - -function getNumber() { - return firebase.remoteConfig.getNumber(rcInstance, getKey()); -} - -function getValue() { - return firebase.remoteConfig.getValue(rcInstance, getKey()); -} - -function getAll() { - return firebase.remoteConfig.getAll(rcInstance); -} - -function getFetchTimeMillis() { - return rcInstance.fetchTimeMillis; -} - -function getLastFetchStatus() { - return rcInstance.lastFetchStatus; -} - -function getSettings() { - return rcInstance.settings; -} - -// Helper functions -function getKey() { - return document.querySelector('#key-input').value; -} - -function handlerWrapper(handler) { - try { - clearOutput(); - var returnValue = handler(); - if (returnValue instanceof Promise) { - handlePromise(returnValue); - } else if (returnValue instanceof Array) { - handleArray(returnValue); - } else if (returnValue instanceof Object) { - handleObject(returnValue); - } else { - displayOutput(returnValue); - } - } catch (error) { - displayOutputError(error); - } -} - -function clearOutput() { - outputBox.innerHTML = ''; -} - -function appendOutput(text) { - outputBox.innerHTML = outputBox.innerHTML + text; -} - -function displayOutput(text) { - outputBox.innerHTML = text; -} - -function displayOutputError(error) { - outputBox.innerHTML = `${error.message || error}`; -} - -function handlePromise(prom) { - const timerId = setInterval(() => appendOutput('.'), 400); - prom - .then(res => { - clearInterval(timerId); - appendOutput(SUCCESS_MESSAGE); - if (res != undefined) { - appendOutput('
'); - appendOutput('return value: ' + res); - } - }) - .catch(e => { - clearInterval(timerId); - appendOutput(`${e}`); - }); -} - -function handleArray(ar) { - displayOutput(ar.toString()); -} - -function handleObject(obj) { - appendOutput('{'); - for (const key in obj) { - if (obj[key] instanceof Function) { - appendOutput(`
${key}: ${obj[key]()},`); - } else if (obj[key] instanceof Object) { - appendOutput(key + ': '); - handleObject(obj[key]); - appendOutput(','); - } else { - appendOutput(`
${key}: ${obj[key]},`); - } - } - appendOutput('
}'); -} - -/** - * Parses string received from input elements into objects. These strings are not JSON - * compatible. - */ -function parseObjFromStr(str, valueIsNumber = false) { - const obj = {}; - str - .substring(str.indexOf('{') + 1, str.indexOf('}')) - .replace(/["']/g, '') // Get rid of curly braces and quotes - .trim() - .split(/[,]/g) // Results in a string of key value separated by a colon - .map(str => str.trim()) - .forEach(keyValue => { - const colIndex = keyValue.indexOf(':'); - const key = keyValue.substring(0, colIndex); - const val = keyValue.substring(colIndex + 1).trim(); - const value = valueIsNumber ? Number(val) : val; - obj[key] = value; - }); - return obj; -} diff --git a/packages-exp/remote-config-exp/tsconfig.json b/packages-exp/remote-config-exp/tsconfig.json deleted file mode 100644 index a4b8678284b..00000000000 --- a/packages-exp/remote-config-exp/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "resolveJsonModule": true, - }, - "exclude": [ - "dist/**/*" - ] -} \ No newline at end of file diff --git a/packages-exp/analytics-compat/.eslintrc.js b/packages/analytics-compat/.eslintrc.js similarity index 100% rename from packages-exp/analytics-compat/.eslintrc.js rename to packages/analytics-compat/.eslintrc.js diff --git a/packages-exp/functions-exp/README.md b/packages/analytics-compat/README.md similarity index 57% rename from packages-exp/functions-exp/README.md rename to packages/analytics-compat/README.md index 6008bd1df1a..cff70fc650f 100644 --- a/packages-exp/functions-exp/README.md +++ b/packages/analytics-compat/README.md @@ -1,5 +1,5 @@ -# `@firebase/functions` +# @firebase/analytics-compat -This is the Firebase Functions component of the Firebase JS SDK. +This is the compatibility layer for the Firebase Analytics component of the Firebase JS SDK. **This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/analytics-compat/karma.conf.js b/packages/analytics-compat/karma.conf.js similarity index 100% rename from packages-exp/analytics-compat/karma.conf.js rename to packages/analytics-compat/karma.conf.js diff --git a/packages-exp/analytics-compat/package.json b/packages/analytics-compat/package.json similarity index 81% rename from packages-exp/analytics-compat/package.json rename to packages/analytics-compat/package.json index 93f9c886411..22fda2c149d 100644 --- a/packages-exp/analytics-compat/package.json +++ b/packages/analytics-compat/package.json @@ -2,7 +2,6 @@ "name": "@firebase/analytics-compat", "version": "0.0.900", "description": "", - "private": true, "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", "browser": "dist/index.esm2017.js", @@ -22,7 +21,7 @@ "typescript": "4.2.2" }, "repository": { - "directory": "packages-exp/analytics-compat", + "directory": "packages/analytics-compat", "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, @@ -34,18 +33,18 @@ "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "build": "rollup -c", "build:deps": "lerna run --scope @firebase/analytics-compat --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js && yarn add-compat-overloads", + "build:release": "yarn build && yarn add-compat-overloads", "dev": "rollup -c -w", "test": "run-p lint test:browser", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", "test:browser": "karma start --single-run", "test:browser:debug": "karma start --browsers=Chrome --auto-watch", - "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../analytics-exp/dist/analytics-exp-public.d.ts -o dist/src/index.d.ts -a -r Analytics:FirebaseAnalytics -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/analytics" + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../analytics/dist/analytics-public.d.ts -o dist/src/index.d.ts -a -r Analytics:FirebaseAnalytics -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/analytics" }, "typings": "dist/src/index.d.ts", "dependencies": { "@firebase/component": "0.5.6", - "@firebase/analytics-exp": "0.0.900", + "@firebase/analytics": "0.6.18", "@firebase/analytics-types": "0.6.0", "@firebase/util": "1.3.0", "tslib": "^2.1.0" diff --git a/packages-exp/messaging-exp/rollup.config.js b/packages/analytics-compat/rollup.config.js similarity index 78% rename from packages-exp/messaging-exp/rollup.config.js rename to packages/analytics-compat/rollup.config.js index 5d7b3338874..c5d161f6e62 100644 --- a/packages-exp/messaging-exp/rollup.config.js +++ b/packages/analytics-compat/rollup.config.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google LLC + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,11 @@ */ import json from '@rollup/plugin-json'; -import pkg from './package.json'; -import typescript from 'typescript'; import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import pkg from './package.json'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); - +const deps = Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)); /** * ES5 Builds */ @@ -35,14 +32,17 @@ const es5BuildPlugins = [ ]; const es5Builds = [ + /** + * Browser Builds + */ { input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, { file: pkg.esm5, format: 'es', sourcemap: true } ], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins } ]; @@ -63,21 +63,17 @@ const es2017BuildPlugins = [ const es2017Builds = [ { + /** + * Browser Build + */ input: 'src/index.ts', output: { file: pkg.browser, format: 'es', sourcemap: true }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - // sw builds - { - input: 'src/index.sw.ts', - output: { file: pkg.sw, format: 'es', sourcemap: true }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins } ]; diff --git a/packages-exp/analytics-compat/src/constants.ts b/packages/analytics-compat/src/constants.ts similarity index 100% rename from packages-exp/analytics-compat/src/constants.ts rename to packages/analytics-compat/src/constants.ts diff --git a/packages-exp/analytics-compat/src/index.ts b/packages/analytics-compat/src/index.ts similarity index 92% rename from packages-exp/analytics-compat/src/index.ts rename to packages/analytics-compat/src/index.ts index eeb46e88f8e..2cc2dfec387 100644 --- a/packages-exp/analytics-compat/src/index.ts +++ b/packages/analytics-compat/src/index.ts @@ -31,22 +31,16 @@ import { import { settings as settingsExp, isSupported as isSupportedExp -} from '@firebase/analytics-exp'; +} from '@firebase/analytics'; import { EventName } from './constants'; -declare module '@firebase/component' { - interface NameServiceMapping { - 'analytics-compat': AnalyticsService; - } -} - const factory: InstanceFactory<'analytics-compat'> = ( container: ComponentContainer ) => { // Dependencies const app = container.getProvider('app-compat').getImmediate(); const analyticsServiceExp = container - .getProvider('analytics-exp') + .getProvider('analytics') .getImmediate(); return new AnalyticsService(app as FirebaseApp, analyticsServiceExp); diff --git a/packages-exp/analytics-compat/src/service.test.ts b/packages/analytics-compat/src/service.test.ts similarity index 98% rename from packages-exp/analytics-compat/src/service.test.ts rename to packages/analytics-compat/src/service.test.ts index 1817a0be314..958f97c525c 100644 --- a/packages-exp/analytics-compat/src/service.test.ts +++ b/packages/analytics-compat/src/service.test.ts @@ -17,7 +17,7 @@ import { expect, use } from 'chai'; import { AnalyticsService } from './service'; import { firebase, FirebaseApp } from '@firebase/app-compat'; -import * as analyticsExp from '@firebase/analytics-exp'; +import * as analyticsExp from '@firebase/analytics'; import { stub, match, SinonStub } from 'sinon'; import * as sinonChai from 'sinon-chai'; diff --git a/packages-exp/analytics-compat/src/service.ts b/packages/analytics-compat/src/service.ts similarity index 98% rename from packages-exp/analytics-compat/src/service.ts rename to packages/analytics-compat/src/service.ts index 283c4478683..ed53cc533d2 100644 --- a/packages-exp/analytics-compat/src/service.ts +++ b/packages/analytics-compat/src/service.ts @@ -28,7 +28,7 @@ import { setCurrentScreen as setCurrentScreenExp, setUserId as setUserIdExp, setUserProperties as setUserPropertiesExp -} from '@firebase/analytics-exp'; +} from '@firebase/analytics'; import { _FirebaseService, FirebaseApp } from '@firebase/app-compat'; export class AnalyticsService implements FirebaseAnalytics, _FirebaseService { diff --git a/packages-exp/analytics-compat/tsconfig.json b/packages/analytics-compat/tsconfig.json similarity index 100% rename from packages-exp/analytics-compat/tsconfig.json rename to packages/analytics-compat/tsconfig.json diff --git a/packages/analytics-types/index.d.ts b/packages/analytics-types/index.d.ts index 49bc5ddf3d1..286a8ee37a3 100644 --- a/packages/analytics-types/index.d.ts +++ b/packages/analytics-types/index.d.ts @@ -679,6 +679,6 @@ export interface ThrottleMetadata { declare module '@firebase/component' { interface NameServiceMapping { - 'analytics': FirebaseAnalytics; + 'analytics-compat': FirebaseAnalytics; } } diff --git a/packages-exp/analytics-exp/api-extractor.json b/packages/analytics/api-extractor.json similarity index 100% rename from packages-exp/analytics-exp/api-extractor.json rename to packages/analytics/api-extractor.json diff --git a/packages/analytics/index.test.ts b/packages/analytics/index.test.ts deleted file mode 100644 index 8b51306d86e..00000000000 --- a/packages/analytics/index.test.ts +++ /dev/null @@ -1,371 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { FirebaseAnalytics } from '@firebase/analytics-types'; -import { SinonStub, stub, useFakeTimers } from 'sinon'; -import './testing/setup'; -import { - settings as analyticsSettings, - factory as analyticsFactory, - resetGlobalVars, - getGlobalVars -} from './index'; -import { - getFakeApp, - getFakeInstallations -} from './testing/get-fake-firebase-services'; -import { FirebaseApp } from '@firebase/app-types'; -import { GtagCommand, EventName } from './src/constants'; -import { findGtagScriptOnPage } from './src/helpers'; -import { removeGtagScript } from './testing/gtag-script-util'; -import { Deferred } from '@firebase/util'; -import { AnalyticsError } from './src/errors'; -import { FirebaseInstallations } from '@firebase/installations-types'; - -let analyticsInstance: FirebaseAnalytics = {} as FirebaseAnalytics; -const fakeMeasurementId = 'abcd-efgh'; -const fakeAppParams = { appId: 'abcdefgh12345:23405', apiKey: 'AAbbCCdd12345' }; -let fetchStub: SinonStub = stub(); -const customGtagName = 'customGtag'; -const customDataLayerName = 'customDataLayer'; -let clock: sinon.SinonFakeTimers; - -// Fake indexedDB.open() request -let fakeRequest = { - onsuccess: () => {}, - result: { - close: () => {} - } -}; -let idbOpenStub = stub(); - -function stubFetch(status: number, body: object): void { - fetchStub = stub(window, 'fetch'); - const mockResponse = new Response(JSON.stringify(body), { - status - }); - fetchStub.returns(Promise.resolve(mockResponse)); -} - -// Stub indexedDB.open() because sinon's clock does not know -// how to wait for the real indexedDB callbacks to resolve. -function stubIdbOpen(): void { - (fakeRequest = { - onsuccess: () => {}, - result: { - close: () => {} - } - }), - (idbOpenStub = stub(indexedDB, 'open').returns(fakeRequest as any)); -} - -describe('FirebaseAnalytics instance tests', () => { - describe('Initialization', () => { - beforeEach(() => resetGlobalVars()); - - it('Throws if no appId in config', () => { - const app = getFakeApp({ apiKey: fakeAppParams.apiKey }); - const installations = getFakeInstallations(); - expect(() => analyticsFactory(app, installations)).to.throw( - AnalyticsError.NO_APP_ID - ); - }); - it('Throws if no apiKey or measurementId in config', () => { - const app = getFakeApp({ appId: fakeAppParams.appId }); - const installations = getFakeInstallations(); - expect(() => analyticsFactory(app, installations)).to.throw( - AnalyticsError.NO_API_KEY - ); - }); - it('Warns if config has no apiKey but does have a measurementId', () => { - // Since this is a warning and doesn't block the rest of initialization - // all the async stuff needs to be stubbed and cleaned up. - const warnStub = stub(console, 'warn'); - const docStub = stub(document, 'createElement'); - stubFetch(200, { measurementId: fakeMeasurementId }); - stubIdbOpen(); - const app = getFakeApp({ - appId: fakeAppParams.appId, - measurementId: fakeMeasurementId - }); - const installations = getFakeInstallations(); - analyticsFactory(app, installations); - expect(warnStub.args[0][1]).to.include( - `Falling back to the measurement ID ${fakeMeasurementId}` - ); - warnStub.restore(); - docStub.restore(); - fetchStub.restore(); - idbOpenStub.restore(); - delete window['gtag']; - delete window['dataLayer']; - }); - it('Throws if creating an instance with already-used appId', () => { - const app = getFakeApp(fakeAppParams); - const installations = getFakeInstallations(); - resetGlobalVars(false, { [fakeAppParams.appId]: Promise.resolve() }); - expect(() => analyticsFactory(app, installations)).to.throw( - AnalyticsError.ALREADY_EXISTS - ); - }); - }); - describe('Standard app, page already has user gtag script', () => { - let app: FirebaseApp = {} as FirebaseApp; - let fidDeferred: Deferred; - const gtagStub: SinonStub = stub(); - before(() => { - clock = useFakeTimers(); - resetGlobalVars(); - app = getFakeApp(fakeAppParams); - fidDeferred = new Deferred(); - const installations = getFakeInstallations('fid-1234', () => - fidDeferred.resolve() - ); - window['gtag'] = gtagStub; - window['dataLayer'] = []; - stubFetch(200, { measurementId: fakeMeasurementId }); - stubIdbOpen(); - analyticsInstance = analyticsFactory(app, installations); - }); - after(() => { - delete window['gtag']; - delete window['dataLayer']; - removeGtagScript(); - fetchStub.restore(); - clock.restore(); - idbOpenStub.restore(); - }); - it('Contains reference to parent app', () => { - expect(analyticsInstance.app).to.equal(app); - }); - it('Calls gtag correctly on logEvent (instance)', async () => { - analyticsInstance.logEvent(EventName.ADD_PAYMENT_INFO, { - currency: 'USD' - }); - // Successfully resolves fake IDB open request. - fakeRequest.onsuccess(); - // Clear promise chain started by logEvent. - await clock.runAllAsync(); - expect(gtagStub).to.have.been.calledWith('js'); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.CONFIG, - fakeMeasurementId, - { - 'firebase_id': 'fid-1234', - origin: 'firebase', - update: true - } - ); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.EVENT, - EventName.ADD_PAYMENT_INFO, - { - 'send_to': 'abcd-efgh', - currency: 'USD' - } - ); - }); - it('setCurrentScreen() method exists on instance', () => { - expect(analyticsInstance.setCurrentScreen).to.be.instanceOf(Function); - }); - it('setUserId() method exists on instance', () => { - expect(analyticsInstance.setUserId).to.be.instanceOf(Function); - }); - it('setUserProperties() method exists on instance', () => { - expect(analyticsInstance.setUserProperties).to.be.instanceOf(Function); - }); - it('setAnalyticsCollectionEnabled() method exists on instance', () => { - expect(analyticsInstance.setAnalyticsCollectionEnabled).to.be.instanceOf( - Function - ); - }); - }); - - describe('Standard app, mismatched environment', () => { - let app: FirebaseApp = {} as FirebaseApp; - let installations: FirebaseInstallations = {} as FirebaseInstallations; - const gtagStub: SinonStub = stub(); - let fidDeferred: Deferred; - let warnStub: SinonStub; - let cookieStub: SinonStub; - beforeEach(() => { - clock = useFakeTimers(); - resetGlobalVars(); - app = getFakeApp(fakeAppParams); - fidDeferred = new Deferred(); - installations = getFakeInstallations('fid-1234', () => - fidDeferred.resolve() - ); - window['gtag'] = gtagStub; - window['dataLayer'] = []; - stubFetch(200, { measurementId: fakeMeasurementId }); - warnStub = stub(console, 'warn'); - stubIdbOpen(); - }); - afterEach(() => { - delete window['gtag']; - delete window['dataLayer']; - removeGtagScript(); - fetchStub.restore(); - clock.restore(); - warnStub.restore(); - idbOpenStub.restore(); - gtagStub.resetHistory(); - }); - it('Warns on initialization if cookies not available', async () => { - cookieStub = stub(navigator, 'cookieEnabled').value(false); - analyticsInstance = analyticsFactory(app, installations); - expect(warnStub.args[0][1]).to.include( - AnalyticsError.INVALID_ANALYTICS_CONTEXT - ); - expect(warnStub.args[0][1]).to.include('Cookies'); - cookieStub.restore(); - }); - it('Warns on initialization if in browser extension', async () => { - window.chrome = { runtime: { id: 'blah' } }; - analyticsInstance = analyticsFactory(app, installations); - expect(warnStub.args[0][1]).to.include( - AnalyticsError.INVALID_ANALYTICS_CONTEXT - ); - expect(warnStub.args[0][1]).to.include('browser extension'); - window.chrome = undefined; - }); - it('Warns on logEvent if indexedDB API not available', async () => { - const idbStub = stub(window, 'indexedDB').value(undefined); - analyticsInstance = analyticsFactory(app, installations); - analyticsInstance.logEvent(EventName.ADD_PAYMENT_INFO, { - currency: 'USD' - }); - // Clear promise chain started by logEvent. - await clock.runAllAsync(); - // gtag config call omits FID - expect(gtagStub).to.be.calledWith('config', 'abcd-efgh', { - update: true, - origin: 'firebase' - }); - expect(warnStub.args[0][1]).to.include( - AnalyticsError.INDEXEDDB_UNAVAILABLE - ); - expect(warnStub.args[0][1]).to.include('IndexedDB is not available'); - idbStub.restore(); - }); - it('Warns on logEvent if indexedDB.open() not allowed', async () => { - idbOpenStub.restore(); - idbOpenStub = stub(indexedDB, 'open').throws('idb open error test'); - analyticsInstance = analyticsFactory(app, installations); - analyticsInstance.logEvent(EventName.ADD_PAYMENT_INFO, { - currency: 'USD' - }); - // Clear promise chain started by logEvent. - await clock.runAllAsync(); - // gtag config call omits FID - expect(gtagStub).to.be.calledWith('config', 'abcd-efgh', { - update: true, - origin: 'firebase' - }); - expect(warnStub.args[0][1]).to.include( - AnalyticsError.INDEXEDDB_UNAVAILABLE - ); - expect(warnStub.args[0][1]).to.include('idb open error test'); - }); - }); - - describe('Page has user gtag script with custom gtag and dataLayer names', () => { - let app: FirebaseApp = {} as FirebaseApp; - let fidDeferred: Deferred; - const gtagStub: SinonStub = stub(); - before(() => { - clock = useFakeTimers(); - resetGlobalVars(); - app = getFakeApp(fakeAppParams); - fidDeferred = new Deferred(); - const installations = getFakeInstallations('fid-1234', () => - fidDeferred.resolve() - ); - window[customGtagName] = gtagStub; - window[customDataLayerName] = []; - analyticsSettings({ - dataLayerName: customDataLayerName, - gtagName: customGtagName - }); - stubIdbOpen(); - stubFetch(200, { measurementId: fakeMeasurementId }); - analyticsInstance = analyticsFactory(app, installations); - }); - after(() => { - delete window[customGtagName]; - delete window[customDataLayerName]; - removeGtagScript(); - fetchStub.restore(); - clock.restore(); - idbOpenStub.restore(); - }); - it('Calls gtag correctly on logEvent (instance)', async () => { - analyticsInstance.logEvent(EventName.ADD_PAYMENT_INFO, { - currency: 'USD' - }); - // Successfully resolves fake IDB open request. - fakeRequest.onsuccess(); - // Clear promise chain started by logEvent. - await clock.runAllAsync(); - expect(gtagStub).to.have.been.calledWith('js'); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.CONFIG, - fakeMeasurementId, - { - 'firebase_id': 'fid-1234', - origin: 'firebase', - update: true - } - ); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.EVENT, - EventName.ADD_PAYMENT_INFO, - { - 'send_to': 'abcd-efgh', - currency: 'USD' - } - ); - }); - }); - - describe('Page has no existing gtag script or dataLayer', () => { - it('Adds the script tag to the page', async () => { - resetGlobalVars(); - const app = getFakeApp(fakeAppParams); - const installations = getFakeInstallations(); - stubFetch(200, {}); - stubIdbOpen(); - analyticsInstance = analyticsFactory(app, installations); - - const { initializationPromisesMap } = getGlobalVars(); - // Successfully resolves fake IDB open request. - fakeRequest.onsuccess(); - await initializationPromisesMap[fakeAppParams.appId]; - expect(findGtagScriptOnPage()).to.not.be.null; - expect(typeof window['gtag']).to.equal('function'); - expect(Array.isArray(window['dataLayer'])).to.be.true; - - delete window['gtag']; - delete window['dataLayer']; - removeGtagScript(); - fetchStub.restore(); - idbOpenStub.restore(); - }); - }); -}); diff --git a/packages/analytics/index.ts b/packages/analytics/index.ts deleted file mode 100644 index 7fff6ac181e..00000000000 --- a/packages/analytics/index.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import firebase from '@firebase/app'; -import '@firebase/installations'; -import { FirebaseAnalytics } from '@firebase/analytics-types'; -import { FirebaseAnalyticsInternal } from '@firebase/analytics-interop-types'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { - factory, - settings, - resetGlobalVars, - getGlobalVars -} from './src/factory'; -import { EventName } from './src/constants'; -import { - Component, - ComponentType, - ComponentContainer -} from '@firebase/component'; -import { ERROR_FACTORY, AnalyticsError } from './src/errors'; -import { - isIndexedDBAvailable, - validateIndexedDBOpenable, - areCookiesEnabled, - isBrowserExtension -} from '@firebase/util'; -import { name, version } from './package.json'; - -declare global { - interface Window { - [key: string]: unknown; - } -} - -/** - * Type constant for Firebase Analytics. - */ -const ANALYTICS_TYPE = 'analytics'; - -export function registerAnalytics(instance: _FirebaseNamespace): void { - instance.INTERNAL.registerComponent( - new Component( - ANALYTICS_TYPE, - container => { - // getImmediate for FirebaseApp will always succeed - const app = container.getProvider('app').getImmediate(); - const installations = container - .getProvider('installations') - .getImmediate(); - - return factory(app, installations); - }, - ComponentType.PUBLIC - ).setServiceProps({ - settings, - EventName, - isSupported - }) - ); - - instance.INTERNAL.registerComponent( - new Component('analytics-internal', internalFactory, ComponentType.PRIVATE) - ); - - instance.registerVersion(name, version); - - function internalFactory( - container: ComponentContainer - ): FirebaseAnalyticsInternal { - try { - const analytics = container.getProvider(ANALYTICS_TYPE).getImmediate(); - return { - logEvent: analytics.logEvent - }; - } catch (e) { - throw ERROR_FACTORY.create(AnalyticsError.INTEROP_COMPONENT_REG_FAILED, { - reason: e - }); - } - } -} - -export { factory, settings, resetGlobalVars, getGlobalVars }; - -registerAnalytics(firebase as _FirebaseNamespace); - -/** - * Define extension behavior of `registerAnalytics` - */ -declare module '@firebase/app-types' { - interface FirebaseNamespace { - analytics(app?: FirebaseApp): FirebaseAnalytics; - } - interface FirebaseApp { - analytics(): FirebaseAnalytics; - } -} - -/** - * this is a public static method provided to users that wraps four different checks: - * - * 1. check if it's not a browser extension environment. - * 1. check if cookie is enabled in current browser. - * 3. check if IndexedDB is supported by the browser environment. - * 4. check if the current browser context is valid for using IndexedDB. - * - */ -async function isSupported(): Promise { - if (isBrowserExtension()) { - return false; - } - if (!areCookiesEnabled()) { - return false; - } - if (!isIndexedDBAvailable()) { - return false; - } - - try { - const isDBOpenable: boolean = await validateIndexedDBOpenable(); - return isDBOpenable; - } catch (error) { - return false; - } -} diff --git a/packages/analytics/package.json b/packages/analytics/package.json index 2b38276d70d..d2b183e836a 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -4,29 +4,32 @@ "description": "A analytics package for new firebase packages", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", "files": [ "dist" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c", + "build": "rollup -c && yarn api-report", + "build:release": "yarn build && yarn typings:public", "build:deps": "lerna run --scope @firebase/analytics --include-dependencies build", "dev": "rollup -c -w", "test": "run-p lint test:all", "test:all": "run-p test:browser test:integration", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", "test:browser": "karma start --single-run --nocache", - "test:integration": "karma start ./karma.integration.conf.js --single-run --nocache" + "test:integration": "karma start ./karma.integration.conf.js --single-run --nocache", + "api-report": "api-extractor run --local --verbose", + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/analytics-public.d.ts" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" }, "dependencies": { - "@firebase/analytics-types": "0.6.0", "@firebase/installations": "0.4.32", "@firebase/logger": "0.2.6", "@firebase/util": "1.3.0", @@ -51,11 +54,12 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/index.d.ts", + "typings": "dist/src/index.d.ts", "nyc": { "extension": [ ".ts" ], "reportDir": "./coverage/node" - } -} + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages/analytics/rollup.config.js b/packages/analytics/rollup.config.js index 003f5e73e9f..6f3e15c1a93 100644 --- a/packages/analytics/rollup.config.js +++ b/packages/analytics/rollup.config.js @@ -20,9 +20,9 @@ import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; import pkg from './package.json'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)) +]; /** * ES5 Builds @@ -39,13 +39,13 @@ const es5Builds = [ * Browser Builds */ { - input: 'index.ts', + input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } + { file: pkg.esm5, format: 'es', sourcemap: true } ], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins } ]; @@ -65,18 +65,18 @@ const es2017BuildPlugins = [ ]; const es2017Builds = [ - /** - * Browser Builds - */ { - input: 'index.ts', + /** + * Browser Build + */ + input: 'src/index.ts', output: { - file: pkg.esm2017, + file: pkg.browser, format: 'es', sourcemap: true }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins } ]; diff --git a/packages-exp/analytics-exp/src/api.test.ts b/packages/analytics/src/api.test.ts similarity index 98% rename from packages-exp/analytics-exp/src/api.test.ts rename to packages/analytics/src/api.test.ts index 90820083128..573468a715e 100644 --- a/packages-exp/analytics-exp/src/api.test.ts +++ b/packages/analytics/src/api.test.ts @@ -20,7 +20,7 @@ import { SinonStub, stub } from 'sinon'; import '../testing/setup'; import { getFullApp } from '../testing/get-fake-firebase-services'; import { getAnalytics, initializeAnalytics } from './api'; -import { FirebaseApp, deleteApp } from '@firebase/app-exp'; +import { FirebaseApp, deleteApp } from '@firebase/app'; import { AnalyticsError } from './errors'; import * as init from './initialize-analytics'; const fakeAppParams = { appId: 'abcdefgh12345:23405', apiKey: 'AAbbCCdd12345' }; diff --git a/packages-exp/analytics-exp/src/api.ts b/packages/analytics/src/api.ts similarity index 99% rename from packages-exp/analytics-exp/src/api.ts rename to packages/analytics/src/api.ts index 38fde73f535..76f8b2007c4 100644 --- a/packages-exp/analytics-exp/src/api.ts +++ b/packages/analytics/src/api.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; +import { _getProvider, FirebaseApp, getApp } from '@firebase/app'; import { Analytics, AnalyticsCallOptions, @@ -69,7 +69,7 @@ declare module '@firebase/component' { export function getAnalytics(app: FirebaseApp = getApp()): Analytics { app = getModularInstance(app); // Dependencies - const analyticsProvider: Provider<'analytics-exp'> = _getProvider( + const analyticsProvider: Provider<'analytics'> = _getProvider( app, ANALYTICS_TYPE ); @@ -93,7 +93,7 @@ export function initializeAnalytics( options: AnalyticsSettings = {} ): Analytics { // Dependencies - const analyticsProvider: Provider<'analytics-exp'> = _getProvider( + const analyticsProvider: Provider<'analytics'> = _getProvider( app, ANALYTICS_TYPE ); diff --git a/packages/analytics/src/constants.ts b/packages/analytics/src/constants.ts index a9b1356f998..6697466c8aa 100644 --- a/packages/analytics/src/constants.ts +++ b/packages/analytics/src/constants.ts @@ -15,6 +15,11 @@ * limitations under the License. */ +/** + * Type constant for Firebase Analytics. + */ +export const ANALYTICS_TYPE = 'analytics'; + // Key to attach FID to in gtag params. export const GA_FID_KEY = 'firebase_id'; export const ORIGIN_KEY = 'origin'; @@ -26,50 +31,8 @@ export const DYNAMIC_CONFIG_URL = export const GTAG_URL = 'https://www.googletagmanager.com/gtag/js'; -export enum GtagCommand { +export const enum GtagCommand { EVENT = 'event', SET = 'set', CONFIG = 'config' } - -/** - * Officially recommended event names for gtag.js - * Any other string is also allowed. - * - * @public - */ -export enum EventName { - ADD_SHIPPING_INFO = 'add_shipping_info', - ADD_PAYMENT_INFO = 'add_payment_info', - ADD_TO_CART = 'add_to_cart', - ADD_TO_WISHLIST = 'add_to_wishlist', - BEGIN_CHECKOUT = 'begin_checkout', - /** - * @deprecated - * This event name is deprecated and is unsupported in updated - * Enhanced Ecommerce reports. - */ - CHECKOUT_PROGRESS = 'checkout_progress', - EXCEPTION = 'exception', - GENERATE_LEAD = 'generate_lead', - LOGIN = 'login', - PAGE_VIEW = 'page_view', - PURCHASE = 'purchase', - REFUND = 'refund', - REMOVE_FROM_CART = 'remove_from_cart', - SCREEN_VIEW = 'screen_view', - SEARCH = 'search', - SELECT_CONTENT = 'select_content', - SELECT_ITEM = 'select_item', - SELECT_PROMOTION = 'select_promotion', - /** @deprecated */ - SET_CHECKOUT_OPTION = 'set_checkout_option', - SHARE = 'share', - SIGN_UP = 'sign_up', - TIMING_COMPLETE = 'timing_complete', - VIEW_CART = 'view_cart', - VIEW_ITEM = 'view_item', - VIEW_ITEM_LIST = 'view_item_list', - VIEW_PROMOTION = 'view_promotion', - VIEW_SEARCH_RESULTS = 'view_search_results' -} diff --git a/packages/analytics/src/errors.ts b/packages/analytics/src/errors.ts index 6381eac4871..98293447c65 100644 --- a/packages/analytics/src/errors.ts +++ b/packages/analytics/src/errors.ts @@ -20,6 +20,7 @@ import { ErrorFactory, ErrorMap } from '@firebase/util'; export const enum AnalyticsError { ALREADY_EXISTS = 'already-exists', ALREADY_INITIALIZED = 'already-initialized', + ALREADY_INITIALIZED_SETTINGS = 'already-initialized-settings', INTEROP_COMPONENT_REG_FAILED = 'interop-component-reg-failed', INVALID_ANALYTICS_CONTEXT = 'invalid-analytics-context', INDEXEDDB_UNAVAILABLE = 'indexeddb-unavailable', @@ -35,6 +36,11 @@ const ERRORS: ErrorMap = { ' already exists. ' + 'Only one Firebase Analytics instance can be created for each appId.', [AnalyticsError.ALREADY_INITIALIZED]: + 'initializeAnalytics() cannot be called again with different options than those ' + + 'it was initially called with. It can be called again with the same options to ' + + 'return the existing instance, or getAnalytics() can be used ' + + 'to get a reference to the already-intialized instance.', + [AnalyticsError.ALREADY_INITIALIZED_SETTINGS]: 'Firebase Analytics has already been initialized.' + 'settings() must be called before initializing any Analytics instance' + 'or it will have no effect.', diff --git a/packages/analytics/src/factory.ts b/packages/analytics/src/factory.ts index 294b8a223da..b0ef0e04aa4 100644 --- a/packages/analytics/src/factory.ts +++ b/packages/analytics/src/factory.ts @@ -15,42 +15,33 @@ * limitations under the License. */ -import { - FirebaseAnalytics, - Gtag, - SettingsOptions, - DynamicConfig, - MinimalDynamicConfig, - AnalyticsCallOptions, - CustomParams, - EventParams -} from '@firebase/analytics-types'; -import { - logEvent, - setCurrentScreen, - setUserId, - setUserProperties, - setAnalyticsCollectionEnabled -} from './functions'; +import { SettingsOptions, Analytics, AnalyticsSettings } from './public-types'; +import { Gtag, DynamicConfig, MinimalDynamicConfig } from './types'; import { getOrCreateDataLayer, wrapOrCreateGtag } from './helpers'; import { AnalyticsError, ERROR_FACTORY } from './errors'; -import { FirebaseApp } from '@firebase/app-types'; -import { FirebaseInstallations } from '@firebase/installations-types'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; import { areCookiesEnabled, isBrowserExtension } from '@firebase/util'; -import { initializeIds } from './initialize-ids'; +import { _initializeAnalytics } from './initialize-analytics'; import { logger } from './logger'; -import { FirebaseService } from '@firebase/app-types/private'; +import { FirebaseApp, _FirebaseService } from '@firebase/app'; -interface FirebaseAnalyticsInternal - extends FirebaseAnalytics, - FirebaseService {} +/** + * Analytics Service class. + */ +export class AnalyticsService implements Analytics, _FirebaseService { + constructor(public app: FirebaseApp) {} + _delete(): Promise { + delete initializationPromisesMap[this.app.options.appId!]; + return Promise.resolve(); + } +} /** * Maps appId to full initialization promise. Wrapped gtag calls must wait on * all or some of these, depending on the call's `send_to` param and the status * of the dynamic config fetches (see below). */ -let initializationPromisesMap: { +export let initializationPromisesMap: { [appId: string]: Promise; // Promise contains measurement ID string. } = {}; @@ -91,7 +82,7 @@ let gtagCoreFunction: Gtag; * Wrapper around gtag function that ensures FID is sent with all * relevant event and config calls. */ -let wrappedGtagFunction: Gtag; +export let wrappedGtagFunction: Gtag; /** * Flag to ensure page initialization steps (creation or wrapping of @@ -101,6 +92,7 @@ let globalInitDone: boolean = false; /** * For testing + * @internal */ export function resetGlobalVars( newGlobalInitDone = false, @@ -116,6 +108,7 @@ export function resetGlobalVars( /** * For testing + * @internal */ export function getGlobalVars(): { initializationPromisesMap: { [appId: string]: Promise }; @@ -130,9 +123,16 @@ export function getGlobalVars(): { } /** - * This must be run before calling firebase.analytics() or it won't + * Configures Firebase Analytics to use custom `gtag` or `dataLayer` names. + * Intended to be used if `gtag.js` script has been installed on + * this page independently of Firebase Analytics, and is using non-default + * names for either the `gtag` function or for `dataLayer`. + * Must be called before calling `getAnalytics()` or it won't * have any effect. - * @param options Custom gtag and dataLayer names. + * + * @public + * + * @param options - Custom gtag and dataLayer names. */ export function settings(options: SettingsOptions): void { if (globalInitDone) { @@ -170,10 +170,15 @@ function warnOnBrowserContextMismatch(): void { } } +/** + * Analytics instance factory. + * @internal + */ export function factory( app: FirebaseApp, - installations: FirebaseInstallations -): FirebaseAnalytics { + installations: _FirebaseInstallationsInternal, + options?: AnalyticsSettings +): AnalyticsService { warnOnBrowserContextMismatch(); const appId = app.options.appId; if (!appId) { @@ -216,69 +221,17 @@ export function factory( } // Async but non-blocking. // This map reflects the completion state of all promises for each appId. - initializationPromisesMap[appId] = initializeIds( + initializationPromisesMap[appId] = _initializeAnalytics( app, dynamicConfigPromisesList, measurementIdToAppId, installations, gtagCoreFunction, - dataLayerName + dataLayerName, + options ); - const analyticsInstance: FirebaseAnalyticsInternal = { - app, - // Public methods return void for API simplicity and to better match gtag, - // while internal implementations return promises. - logEvent: ( - eventName: string, - eventParams?: EventParams | CustomParams, - options?: AnalyticsCallOptions - ) => { - logEvent( - wrappedGtagFunction, - initializationPromisesMap[appId], - eventName, - eventParams, - options - ).catch(e => logger.error(e)); - }, - setCurrentScreen: (screenName, options) => { - setCurrentScreen( - wrappedGtagFunction, - initializationPromisesMap[appId], - screenName, - options - ).catch(e => logger.error(e)); - }, - setUserId: (id, options) => { - setUserId( - wrappedGtagFunction, - initializationPromisesMap[appId], - id, - options - ).catch(e => logger.error(e)); - }, - setUserProperties: (properties, options) => { - setUserProperties( - wrappedGtagFunction, - initializationPromisesMap[appId], - properties, - options - ).catch(e => logger.error(e)); - }, - setAnalyticsCollectionEnabled: enabled => { - setAnalyticsCollectionEnabled( - initializationPromisesMap[appId], - enabled - ).catch(e => logger.error(e)); - }, - INTERNAL: { - delete: (): Promise => { - delete initializationPromisesMap[appId]; - return Promise.resolve(); - } - } - }; + const analyticsInstance: AnalyticsService = new AnalyticsService(app); return analyticsInstance; } diff --git a/packages/analytics/src/functions.test.ts b/packages/analytics/src/functions.test.ts index 531bb6faa10..bf4f08dbd95 100644 --- a/packages/analytics/src/functions.test.ts +++ b/packages/analytics/src/functions.test.ts @@ -25,7 +25,7 @@ import { setUserProperties, setAnalyticsCollectionEnabled } from './functions'; -import { GtagCommand, EventName } from './constants'; +import { GtagCommand } from './constants'; const fakeMeasurementId = 'abcd-efgh-ijkl'; const fakeInitializationPromise = Promise.resolve(fakeMeasurementId); @@ -38,57 +38,45 @@ describe('FirebaseAnalytics methods', () => { }); it('logEvent() calls gtag function correctly', async () => { - await logEvent(gtagStub, fakeInitializationPromise, EventName.ADD_TO_CART, { + await logEvent(gtagStub, fakeInitializationPromise, 'add_to_cart', { currency: 'USD' }); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.EVENT, - EventName.ADD_TO_CART, - { - 'send_to': fakeMeasurementId, - currency: 'USD' - } - ); + expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'add_to_cart', { + 'send_to': fakeMeasurementId, + currency: 'USD' + }); }); it('logEvent() with no event params calls gtag function correctly', async () => { - await logEvent(gtagStub, fakeInitializationPromise, EventName.VIEW_ITEM); + await logEvent(gtagStub, fakeInitializationPromise, 'view_item'); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.EVENT, - EventName.VIEW_ITEM, - { - 'send_to': fakeMeasurementId - } - ); + expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'view_item', { + 'send_to': fakeMeasurementId + }); }); it('logEvent() globally calls gtag function correctly', async () => { await logEvent( gtagStub, fakeInitializationPromise, - EventName.ADD_TO_CART, + 'add_to_cart', { currency: 'USD' }, { global: true } ); - expect(gtagStub).to.have.been.calledWith( - GtagCommand.EVENT, - EventName.ADD_TO_CART, - { - currency: 'USD' - } - ); + expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'add_to_cart', { + currency: 'USD' + }); }); it('logEvent() with no event params globally calls gtag function correctly', async () => { await logEvent( gtagStub, fakeInitializationPromise, - EventName.ADD_TO_CART, + 'add_to_cart', undefined, { global: true @@ -97,7 +85,7 @@ describe('FirebaseAnalytics methods', () => { expect(gtagStub).to.have.been.calledWith( GtagCommand.EVENT, - EventName.ADD_TO_CART, + 'add_to_cart', undefined ); }); diff --git a/packages/analytics/src/functions.ts b/packages/analytics/src/functions.ts index 0ef739460c8..15c397f5799 100644 --- a/packages/analytics/src/functions.ts +++ b/packages/analytics/src/functions.ts @@ -17,11 +17,11 @@ import { AnalyticsCallOptions, - Gtag, CustomParams, ControlParams, EventParams -} from '@firebase/analytics-types'; +} from './public-types'; +import { Gtag } from './types'; import { GtagCommand } from './constants'; /** * Logs an analytics event through the Firebase SDK. diff --git a/packages/analytics/src/get-config.test.ts b/packages/analytics/src/get-config.test.ts index 430f07b66fc..84aafaa412d 100644 --- a/packages/analytics/src/get-config.test.ts +++ b/packages/analytics/src/get-config.test.ts @@ -26,7 +26,7 @@ import { } from './get-config'; import { DYNAMIC_CONFIG_URL } from './constants'; import { getFakeApp } from '../testing/get-fake-firebase-services'; -import { DynamicConfig, MinimalDynamicConfig } from '@firebase/analytics-types'; +import { DynamicConfig, MinimalDynamicConfig } from './types'; import { AnalyticsError } from './errors'; const fakeMeasurementId = 'abcd-efgh-ijkl'; diff --git a/packages/analytics/src/get-config.ts b/packages/analytics/src/get-config.ts index 6be89ce4876..0b41c88edf0 100644 --- a/packages/analytics/src/get-config.ts +++ b/packages/analytics/src/get-config.ts @@ -19,12 +19,8 @@ * @fileoverview Most logic is copied from packages/remote-config/src/client/retrying_client.ts */ -import { FirebaseApp } from '@firebase/app-types'; -import { - DynamicConfig, - ThrottleMetadata, - MinimalDynamicConfig -} from '@firebase/analytics-types'; +import { FirebaseApp } from '@firebase/app'; +import { DynamicConfig, ThrottleMetadata, MinimalDynamicConfig } from './types'; import { FirebaseError, calculateBackoffMillis } from '@firebase/util'; import { AnalyticsError, ERROR_FACTORY } from './errors'; import { DYNAMIC_CONFIG_URL, FETCH_TIMEOUT_MILLIS } from './constants'; diff --git a/packages/analytics/src/helpers.test.ts b/packages/analytics/src/helpers.test.ts index 9e82f6600a5..79614f9edf4 100644 --- a/packages/analytics/src/helpers.test.ts +++ b/packages/analytics/src/helpers.test.ts @@ -18,12 +18,13 @@ import { expect } from 'chai'; import { SinonStub, stub } from 'sinon'; import '../testing/setup'; -import { DataLayer, Gtag, DynamicConfig } from '@firebase/analytics-types'; +import { DataLayer, Gtag, DynamicConfig } from './types'; import { getOrCreateDataLayer, insertScriptTag, wrapOrCreateGtag, - findGtagScriptOnPage + findGtagScriptOnPage, + promiseAllSettled } from './helpers'; import { GtagCommand } from './constants'; import { Deferred } from '@firebase/util'; @@ -100,7 +101,7 @@ describe('Gtag wrapping functions', () => { initPromise2.resolve('other-measurement-id'); // Resolves second initialization promise. await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - await Promise.all(fakeDynamicConfigPromises); + await promiseAllSettled(fakeDynamicConfigPromises); expect((window['dataLayer'] as DataLayer).length).to.equal(1); }); @@ -133,7 +134,7 @@ describe('Gtag wrapping functions', () => { initPromise2.resolve(); // Resolves second initialization promise. await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() - await Promise.all(fakeDynamicConfigPromises); + await promiseAllSettled(fakeDynamicConfigPromises); expect((window['dataLayer'] as DataLayer).length).to.equal(1); } @@ -195,7 +196,7 @@ describe('Gtag wrapping functions', () => { expect((window['dataLayer'] as DataLayer).length).to.equal(0); initPromise1.resolve(); // Resolves first initialization promise. - await Promise.all(fakeDynamicConfigPromises); + await promiseAllSettled(fakeDynamicConfigPromises); await Promise.all([initPromise1]); // Wait for resolution of Promise.all() expect((window['dataLayer'] as DataLayer).length).to.equal(1); @@ -241,7 +242,7 @@ describe('Gtag wrapping functions', () => { expect((window['dataLayer'] as DataLayer).length).to.equal(0); initPromise1.resolve(fakeMeasurementId); - await Promise.all(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. expect((window['dataLayer'] as DataLayer).length).to.equal(0); await Promise.all([initPromise1]); // Wait for resolution of Promise.all() @@ -255,7 +256,7 @@ describe('Gtag wrapping functions', () => { (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { 'transaction_id': 'abcd123' }); - await Promise.all(fakeDynamicConfigPromises); + await promiseAllSettled(fakeDynamicConfigPromises); await Promise.resolve(); // Config call is always chained onto initialization promise list, even if empty. expect((window['dataLayer'] as DataLayer).length).to.equal(1); }); @@ -295,7 +296,7 @@ describe('Gtag wrapping functions', () => { expect(existingGtagStub).to.not.be.called; initPromise2.resolve(); // Resolves second initialization promise. - await Promise.all(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. expect(existingGtagStub).to.not.be.called; await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() @@ -332,7 +333,7 @@ describe('Gtag wrapping functions', () => { expect(existingGtagStub).to.not.be.called; initPromise2.resolve(); // Resolves second initialization promise. - await Promise.all(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. expect(existingGtagStub).to.not.be.called; await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() @@ -407,7 +408,7 @@ describe('Gtag wrapping functions', () => { expect(existingGtagStub).to.not.be.called; initPromise1.resolve(); // Resolves first initialization promise. - await Promise.all(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. expect(existingGtagStub).to.not.be.called; await Promise.all([initPromise1]); // Wait for resolution of Promise.all() @@ -463,7 +464,7 @@ describe('Gtag wrapping functions', () => { expect(existingGtagStub).to.not.be.called; initPromise1.resolve(fakeMeasurementId); - await Promise.all(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. expect(existingGtagStub).to.not.be.called; await Promise.all([initPromise1]); // Wait for resolution of Promise.all() @@ -483,7 +484,7 @@ describe('Gtag wrapping functions', () => { (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { 'transaction_id': 'abcd123' }); - await Promise.all(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. expect(existingGtagStub).to.not.be.called; await Promise.resolve(); // Config call is always chained onto initialization promise list, even if empty. expect(existingGtagStub).to.be.calledWith( diff --git a/packages/analytics/src/helpers.ts b/packages/analytics/src/helpers.ts index 4a474d540ab..d8171f9c0f5 100644 --- a/packages/analytics/src/helpers.ts +++ b/packages/analytics/src/helpers.ts @@ -15,18 +15,23 @@ * limitations under the License. */ -import { - DynamicConfig, - DataLayer, - Gtag, - CustomParams, - ControlParams, - EventParams, - MinimalDynamicConfig -} from '@firebase/analytics-types'; +import { CustomParams, ControlParams, EventParams } from './public-types'; +import { DynamicConfig, DataLayer, Gtag, MinimalDynamicConfig } from './types'; import { GtagCommand, GTAG_URL } from './constants'; import { logger } from './logger'; +/** + * Makeshift polyfill for Promise.allSettled(). Resolves when all promises + * have either resolved or rejected. + * + * @param promises Array of promises to wait for. + */ +export function promiseAllSettled( + promises: Array> +): Promise { + return Promise.all(promises.map(promise => promise.catch(e => e))); +} + /** * Inserts gtag script tag into the page to asynchronously download gtag. * @param dataLayerName Name of datalayer (most often the default, "_dataLayer"). @@ -36,6 +41,8 @@ export function insertScriptTag( measurementId: string ): void { const script = document.createElement('script'); + // We are not providing an analyticsId in the URL because it would trigger a `page_view` + // without fid. We will initialize ga-id using gtag (config) command together with fid. script.src = `${GTAG_URL}?l=${dataLayerName}&id=${measurementId}`; script.async = true; document.head.appendChild(script); @@ -87,7 +94,9 @@ async function gtagOnConfig( // find the appId (if any) corresponding to this measurementId. If there is one, wait on // that appId's initialization promise. If there is none, promise resolves and gtag // call goes through. - const dynamicConfigResults = await Promise.all(dynamicConfigPromisesList); + const dynamicConfigResults = await promiseAllSettled( + dynamicConfigPromisesList + ); const foundConfig = dynamicConfigResults.find( config => config.measurementId === measurementId ); @@ -132,7 +141,9 @@ async function gtagOnEvent( } // Checking 'send_to' fields requires having all measurement ID results back from // the dynamic config fetch. - const dynamicConfigResults = await Promise.all(dynamicConfigPromisesList); + const dynamicConfigResults = await promiseAllSettled( + dynamicConfigPromisesList + ); for (const sendToId of gaSendToList) { // Any fetched dynamic measurement ID that matches this 'send_to' ID const foundConfig = dynamicConfigResults.find( @@ -241,7 +252,7 @@ function wrapGtag( logger.error(e); } } - return gtagWrapper; + return gtagWrapper as Gtag; } /** diff --git a/packages-exp/analytics-exp/src/index.test.ts b/packages/analytics/src/index.test.ts similarity index 99% rename from packages-exp/analytics-exp/src/index.test.ts rename to packages/analytics/src/index.test.ts index 43bb2ea69a1..607c2e74a5d 100644 --- a/packages-exp/analytics-exp/src/index.test.ts +++ b/packages/analytics/src/index.test.ts @@ -23,7 +23,7 @@ import { getFakeApp, getFakeInstallations } from '../testing/get-fake-firebase-services'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { GtagCommand } from './constants'; import { findGtagScriptOnPage } from './helpers'; import { removeGtagScript } from '../testing/gtag-script-util'; @@ -36,7 +36,7 @@ import { resetGlobalVars, factory as analyticsFactory } from './factory'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; let analyticsInstance: AnalyticsService = {} as AnalyticsService; const fakeMeasurementId = 'abcd-efgh'; diff --git a/packages-exp/analytics-exp/src/index.ts b/packages/analytics/src/index.ts similarity index 93% rename from packages-exp/analytics-exp/src/index.ts rename to packages/analytics/src/index.ts index d4050799802..3974400462b 100644 --- a/packages-exp/analytics-exp/src/index.ts +++ b/packages/analytics/src/index.ts @@ -21,7 +21,7 @@ * limitations under the License. */ -import { registerVersion, _registerComponent } from '@firebase/app-exp'; +import { registerVersion, _registerComponent } from '@firebase/app'; import { FirebaseAnalyticsInternal } from '@firebase/analytics-interop-types'; import { factory } from './factory'; import { ANALYTICS_TYPE } from './constants'; @@ -35,7 +35,7 @@ import { ERROR_FACTORY, AnalyticsError } from './errors'; import { logEvent } from './api'; import { name, version } from '../package.json'; import { AnalyticsCallOptions } from './public-types'; -import '@firebase/installations-exp'; +import '@firebase/installations'; declare global { interface Window { @@ -49,9 +49,9 @@ function registerAnalytics(): void { ANALYTICS_TYPE, (container, { options: analyticsOptions }: InstanceFactoryOptions) => { // getImmediate for FirebaseApp will always succeed - const app = container.getProvider('app-exp').getImmediate(); + const app = container.getProvider('app').getImmediate(); const installations = container - .getProvider('installations-exp-internal') + .getProvider('installations-internal') .getImmediate(); return factory(app, installations, analyticsOptions); diff --git a/packages-exp/analytics-exp/src/initialize-analytics.test.ts b/packages/analytics/src/initialize-analytics.test.ts similarity index 98% rename from packages-exp/analytics-exp/src/initialize-analytics.test.ts rename to packages/analytics/src/initialize-analytics.test.ts index a6152a4dc34..34c799fc83c 100644 --- a/packages-exp/analytics-exp/src/initialize-analytics.test.ts +++ b/packages/analytics/src/initialize-analytics.test.ts @@ -25,9 +25,9 @@ import { } from '../testing/get-fake-firebase-services'; import { GtagCommand } from './constants'; import { DynamicConfig } from './types'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { Deferred } from '@firebase/util'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; import { removeGtagScript } from '../testing/gtag-script-util'; const fakeMeasurementId = 'abcd-efgh-ijkl'; diff --git a/packages-exp/analytics-exp/src/initialize-analytics.ts b/packages/analytics/src/initialize-analytics.ts similarity index 98% rename from packages-exp/analytics-exp/src/initialize-analytics.ts rename to packages/analytics/src/initialize-analytics.ts index 8e3ad0f5a44..2b9c42847f8 100644 --- a/packages-exp/analytics-exp/src/initialize-analytics.ts +++ b/packages/analytics/src/initialize-analytics.ts @@ -17,10 +17,10 @@ import { DynamicConfig, Gtag, MinimalDynamicConfig } from './types'; import { GtagCommand, GA_FID_KEY, ORIGIN_KEY } from './constants'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; import { fetchDynamicConfigWithRetry } from './get-config'; import { logger } from './logger'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { isIndexedDBAvailable, validateIndexedDBOpenable diff --git a/packages/analytics/src/initialize-ids.test.ts b/packages/analytics/src/initialize-ids.test.ts deleted file mode 100644 index 425191aea89..00000000000 --- a/packages/analytics/src/initialize-ids.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import '../testing/setup'; -import { initializeIds } from './initialize-ids'; -import { - getFakeApp, - getFakeInstallations -} from '../testing/get-fake-firebase-services'; -import { GtagCommand } from './constants'; -import { DynamicConfig } from '@firebase/analytics-types'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import { FirebaseApp } from '@firebase/app-types'; -import { Deferred } from '@firebase/util'; -import { removeGtagScript } from '../testing/gtag-script-util'; - -const fakeMeasurementId = 'abcd-efgh-ijkl'; -const fakeFid = 'fid-1234-zyxw'; -const fakeAppId = 'abcdefgh12345:23405'; -const fakeAppParams = { appId: fakeAppId, apiKey: 'AAbbCCdd12345' }; -let fetchStub: SinonStub; - -function stubFetch(): void { - fetchStub = stub(window, 'fetch'); - const mockResponse = new window.Response( - JSON.stringify({ measurementId: fakeMeasurementId, appId: fakeAppId }), - { - status: 200 - } - ); - fetchStub.returns(Promise.resolve(mockResponse)); -} - -describe('initializeIds()', () => { - const gtagStub: SinonStub = stub(); - const dynamicPromisesList: Array> = []; - const measurementIdToAppId: { [key: string]: string } = {}; - let app: FirebaseApp; - let installations: FirebaseInstallations; - let fidDeferred: Deferred; - beforeEach(() => { - fidDeferred = new Deferred(); - app = getFakeApp(fakeAppParams); - installations = getFakeInstallations(fakeFid, fidDeferred.resolve); - }); - afterEach(() => { - fetchStub.restore(); - removeGtagScript(); - }); - it('gets FID and measurement ID and calls gtag config with them', async () => { - stubFetch(); - await initializeIds( - app, - dynamicPromisesList, - measurementIdToAppId, - installations, - gtagStub, - 'dataLayer' - ); - expect(gtagStub).to.be.calledWith(GtagCommand.CONFIG, fakeMeasurementId, { - 'firebase_id': fakeFid, - 'origin': 'firebase', - update: true - }); - }); - it('puts dynamic fetch promise into dynamic promises list', async () => { - stubFetch(); - await initializeIds( - app, - dynamicPromisesList, - measurementIdToAppId, - installations, - gtagStub, - 'dataLayer' - ); - const dynamicPromiseResult = await dynamicPromisesList[0]; - expect(dynamicPromiseResult.measurementId).to.equal(fakeMeasurementId); - expect(dynamicPromiseResult.appId).to.equal(fakeAppId); - }); - it('puts dynamically fetched measurementId into lookup table', async () => { - stubFetch(); - await initializeIds( - app, - dynamicPromisesList, - measurementIdToAppId, - installations, - gtagStub, - 'dataLayer' - ); - expect(measurementIdToAppId[fakeMeasurementId]).to.equal(fakeAppId); - }); - it('warns on local/fetched measurement ID mismatch', async () => { - stubFetch(); - const consoleStub = stub(console, 'warn'); - await initializeIds( - getFakeApp({ ...fakeAppParams, measurementId: 'old-measurement-id' }), - dynamicPromisesList, - measurementIdToAppId, - installations, - gtagStub, - 'dataLayer' - ); - expect(consoleStub.args[0][1]).to.include(fakeMeasurementId); - expect(consoleStub.args[0][1]).to.include('old-measurement-id'); - expect(consoleStub.args[0][1]).to.include('does not match'); - consoleStub.restore(); - }); -}); diff --git a/packages/analytics/src/initialize-ids.ts b/packages/analytics/src/initialize-ids.ts deleted file mode 100644 index 6c58fe8e440..00000000000 --- a/packages/analytics/src/initialize-ids.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - DynamicConfig, - Gtag, - MinimalDynamicConfig -} from '@firebase/analytics-types'; -import { GtagCommand, GA_FID_KEY, ORIGIN_KEY } from './constants'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import { fetchDynamicConfigWithRetry } from './get-config'; -import { logger } from './logger'; -import { FirebaseApp } from '@firebase/app-types'; -import { - isIndexedDBAvailable, - validateIndexedDBOpenable -} from '@firebase/util'; -import { ERROR_FACTORY, AnalyticsError } from './errors'; -import { findGtagScriptOnPage, insertScriptTag } from './helpers'; - -async function validateIndexedDB(): Promise { - if (!isIndexedDBAvailable()) { - logger.warn( - ERROR_FACTORY.create(AnalyticsError.INDEXEDDB_UNAVAILABLE, { - errorInfo: 'IndexedDB is not available in this environment.' - }).message - ); - return false; - } else { - try { - await validateIndexedDBOpenable(); - } catch (e) { - logger.warn( - ERROR_FACTORY.create(AnalyticsError.INDEXEDDB_UNAVAILABLE, { - errorInfo: e - }).message - ); - return false; - } - } - return true; -} - -/** - * Initialize the analytics instance in gtag.js by calling config command with fid. - * - * NOTE: We combine analytics initialization and setting fid together because we want fid to be - * part of the `page_view` event that's sent during the initialization - * @param app Firebase app - * @param gtagCore The gtag function that's not wrapped. - * @param dynamicConfigPromisesList Array of all dynamic config promises. - * @param measurementIdToAppId Maps measurementID to appID. - * @param installations FirebaseInstallations instance. - * - * @returns Measurement ID. - */ -export async function initializeIds( - app: FirebaseApp, - dynamicConfigPromisesList: Array< - Promise - >, - measurementIdToAppId: { [key: string]: string }, - installations: FirebaseInstallations, - gtagCore: Gtag, - dataLayerName: string -): Promise { - const dynamicConfigPromise = fetchDynamicConfigWithRetry(app); - // Once fetched, map measurementIds to appId, for ease of lookup in wrapped gtag function. - dynamicConfigPromise - .then(config => { - measurementIdToAppId[config.measurementId] = config.appId; - if ( - app.options.measurementId && - config.measurementId !== app.options.measurementId - ) { - logger.warn( - `The measurement ID in the local Firebase config (${app.options.measurementId})` + - ` does not match the measurement ID fetched from the server (${config.measurementId}).` + - ` To ensure analytics events are always sent to the correct Analytics property,` + - ` update the` + - ` measurement ID field in the local config or remove it from the local config.` - ); - } - }) - .catch(e => logger.error(e)); - // Add to list to track state of all dynamic config promises. - dynamicConfigPromisesList.push(dynamicConfigPromise); - - const fidPromise: Promise = validateIndexedDB().then( - envIsValid => { - if (envIsValid) { - return installations.getId(); - } else { - return undefined; - } - } - ); - - const [dynamicConfig, fid] = await Promise.all([ - dynamicConfigPromise, - fidPromise - ]); - - // Detect if user has already put the gtag - - - - -

Firebase Auth Tests

-
- - - diff --git a/packages/auth/buildtools/common.py b/packages/auth/buildtools/common.py deleted file mode 100644 index c5a473ba4e7..00000000000 --- a/packages/auth/buildtools/common.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2016 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Common methods and constants for generating test files.""" -import os - - -# The directory in which test HTML files are generated. -TESTS_BASE_PATH = "./generated/tests/" - - -def cd_to_firebaseauth_root(): - """Changes the current directory to the firebase-auth root directory. - This method assumes that this script is in the buildtools/ directory, which is - a direct child of the root directory. - This allows us to avoid writing to the wrong files. - """ - root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - os.chdir(root_dir) - - -def get_files_with_suffix(root, suffix): - """Yields file names under a directory with a given suffix. - Args: - root: The path to the directory where we wish to search. - suffix: The suffix we wish to search for. - Yields: - The paths to files under the directory that have the given suffix. - """ - for root, _, files in os.walk(root): - for file_name in files: - if file_name.endswith(suffix): - yield os.path.join(root, file_name) diff --git a/packages/auth/buildtools/gen_all_tests_js.py b/packages/auth/buildtools/gen_all_tests_js.py deleted file mode 100644 index ced11b5d9de..00000000000 --- a/packages/auth/buildtools/gen_all_tests_js.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2016 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Generates the all_tests.js file. -all_tests.js tells all_tests.html the paths to the files to test. -Usage: -$ python ./buildtools/gen_all_tests_js.py > generated/all_tests.js -""" - -import common - - -def main(): - common.cd_to_firebaseauth_root() - print "var allTests = [" - _print_test_files_under_root(common.TESTS_BASE_PATH) - print "];" - # The following is required in the context of protractor. - print "if (typeof module !== 'undefined' && module.exports) {" - print " module.exports = allTests;" - print "}" - - -def _print_test_files_under_root(root): - """Prints all test HTML files found under a given directory (recursively). - Args: - root: The path to the directory. - """ - for file_name in common.get_files_with_suffix(root, "_test.html"): - print " '%s'," % file_name[2:] # Ignore the beginning './'. - - -if __name__ == "__main__": - main() diff --git a/packages/auth/buildtools/gen_test_html.py b/packages/auth/buildtools/gen_test_html.py deleted file mode 100644 index 90a2362c268..00000000000 --- a/packages/auth/buildtools/gen_test_html.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright 2016 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Generates *_test.html files from *_test.js files. -This modifies files in place and will overwrite existing *_test.html files. -Usage: -$ python ./buildtools/gen_test_html.py -""" - -from collections import namedtuple -import os -import re -from string import Template -import common - - -# Stores the paths of files related to a test file -# (e.g. *_test.html, *_test_dom.html) -RelatedPaths = namedtuple("RelatedPaths", ["html", "dom"]) - - -# The root-level directories containing JS tests. -DIRECTORIES_WITH_TESTS = ["test"] - - -def main(): - common.cd_to_firebaseauth_root() - template_data = _read_file("./buildtools/test_template.html") - template = Template(template_data) - for directory in DIRECTORIES_WITH_TESTS: - for js_path in common.get_files_with_suffix(directory, "_test.js"): - _gen_html(js_path, template) - - -def _gen_html(js_path, template): - """Generates a Closure test HTML wrapper file and saves it to the filesystem. - Args: - js_path: The path to the JS test (*_test.js) file. - template: The template for the HTML wrapper. - """ - try: - related_paths = _get_related_paths_from_js_path(js_path) - js_data = _read_file(js_path) - dom = (_read_file(related_paths.dom) - if os.path.isfile(related_paths.dom) else "") - package = _extract_closure_package(js_data) - generated_html = template.substitute(package=package, dom=dom) - - _write_file(related_paths.html, generated_html) - - except: # pylint: disable=bare-except - print "HTML generation failed for: %s" % js_path - - -def _get_related_paths_from_js_path(js_path): - """Converts the JS test file path to paths of related files. - For example, ./path/to/foo_test.js becomes - ./generated/tests/path/to/foo_test.html - ./path/to/foo_test_dom.html - Args: - js_path: The path to the JS test (*_test.js) file. - Returns: - The paths to the related files, as a RelatedPaths. - """ - base_name = os.path.splitext(js_path)[0] - return RelatedPaths(common.TESTS_BASE_PATH + base_name + ".html", - base_name + "_dom.html") - - -def _extract_closure_package(js_data): - """Extracts the package name that is goog.provide()d in the JS file. - Args: - js_data: The contents of a JS test (*_test.js) file. - Returns: - The closure package goog.provide()d by the file. - Raises: - ValueError: The JS does not contain a goog.provide(). - """ - matches = re.search(r"goog\.provide\('(.+)'\);", js_data) - if matches is None: - raise ValueError("goog.provide() not found in file") - return matches.group(1) - - -def _read_file(path): - """Reads a file into a string. - Args: - path: The path to a file. - Returns: - The contents of the file. - """ - with open(path) as f: - return f.read() - - -def _write_file(path, contents): - """Writes a string to file, overwriting existing content. - Intermediate directories are created if not present. - Args: - path: The path to a file. - contents: The string to write to the file. - """ - dir_name = os.path.dirname(path) - if not os.path.exists(dir_name): - os.makedirs(dir_name) - with open(path, "w") as f: - f.write(contents) - - -if __name__ == "__main__": - main() diff --git a/packages/auth/buildtools/generate_test_files.sh b/packages/auth/buildtools/generate_test_files.sh deleted file mode 100755 index 1d5ead75d06..00000000000 --- a/packages/auth/buildtools/generate_test_files.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# Copyright 2016 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Generates the temporary files needed for tests to run, putting them in the -# generated/ directory. -# -# Usage: -# $ buildtools/generate_test_files.sh - -# CD to the root firebase-auth directory, which should be the parent directory of -# buildtools/. -cd "$(dirname $(dirname "$0"))" -mkdir -p generated - -echo "Generating dependency file..." -python2 ../../node_modules/google-closure-library/closure/bin/build/depswriter.py \ - --root_with_prefix="test ../../../../test" \ - --root_with_prefix="src ../../../../src" \ - > generated/deps.js - -echo "Generating test HTML files..." -python2 ./buildtools/gen_test_html.py -python2 ./buildtools/gen_all_tests_js.py > generated/all_tests.js - -echo "Done." diff --git a/packages/auth/buildtools/run_demo.sh b/packages/auth/buildtools/run_demo.sh deleted file mode 100755 index 0f5feaafb3c..00000000000 --- a/packages/auth/buildtools/run_demo.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# Copyright 2017 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Runs the server for the demo page. -# -# Usage: -# $ buildtools/run_demo.sh - -# CD to the root packages/auth directory, which should be the parent directory of -# buildtools/. -cd "$(dirname $(dirname "$0"))" -# Go back to repo root and build all binaries needed for the demo app. -cd ../.. -yarn build:release -# Go back to Auth package. -cd packages/auth -# Make dist directory if not already there. -mkdir -p demo/public/dist -# Copy app, auth and database binaries to demo dist directory. -cp ../firebase/firebase-app.js demo/public/dist/firebase-app.js -cp ../firebase/firebase-auth.js demo/public/dist/firebase-auth.js -cp ../firebase/firebase-database.js demo/public/dist/firebase-database.js -# Serve demo app. -cd demo -`yarn bin`/firebase emulators:start diff --git a/packages/auth/buildtools/run_tests.sh b/packages/auth/buildtools/run_tests.sh deleted file mode 100755 index 2661ae73320..00000000000 --- a/packages/auth/buildtools/run_tests.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/bin/bash -# Copyright 2016 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Prepares the setup for running unit tests. It starts a Selenium Webdriver. -# creates a local webserver to serve test files, and run protractor. -# -# Usage: -# -# ./buildtools/run_tests.sh [--saucelabs [--tunnelIdentifier=]] -# -# Can take up to two arguments: -# --saucelabs: Use SauceLabs instead of local Chrome and Firefox. -# --tunnelIdentifier=: when using SauceLabs, specify the tunnel -# identifier. Otherwise, uses the environment variable TRAVIS_JOB_NUMBER. -# -# Prefer to use the `npm test` command as explained below. -# -# Run locally with Chrome & Firefox: -# $ npm test -# It will start a local Selenium Webdriver server as well as the HTTP server -# that serves test files. -# -# Run locally using SauceLabs: -# Go to your SauceLab account, under "My Account", and copy paste the -# access key. Now export the following variables: -# $ export SAUCE_USERNAME= -# $ export SAUCE_ACCESS_KEY= -# Then, start SauceConnect: -# $ ./buildtools/sauce_connect.sh -# Take note of the "Tunnel Identifier" value logged in the terminal. -# Run the tests: -# $ npm run -- --saucelabs --tunnelIdentifier= -# This will start the HTTP Server locally, and connect through SauceConnect -# to SauceLabs remote browsers instances. -# -# Travis will run `npm test -- --saucelabs`. - -# Since yarn workspaces might hoist our packages, we have to fallback to -# ../../node_modules when we want to execute a script. -declare -a nodeModulesBasedirs=( - "./node_modules/.bin" - "../../node_modules/.bin" -) - -# Tries to resolve the first argument as an npm executable, taking hoisting into -# account. If case of a successful resolution, executes the binary, passing -# the rest of given arguments to an invocation. -function evalModule { - for basedir in "${nodeModulesBasedirs[@]}" - do - if [ -f "$basedir/$1" ]; then - eval "$basedir/$1 ${@:2}" - ret=$? - if [ $ret -ne 0 ]; then - exit $ret - fi - break - fi - done -} - -cd "$(dirname $(dirname "$0"))" - -function killServer () { - if [ "$seleniumStarted" = true ]; then - echo "Stopping Selenium..." - evalModule webdriver-manager shutdown - evalModule webdriver-manager clean - # Selenium is not getting shutdown. Send a kill signal. - lsof -t -i :4444 | xargs kill - fi - echo "Killing HTTP Server..." - kill $serverPid -} - -# Start the local webserver. -evalModule gulp "serve &" -serverPid=$! -echo "Local HTTP Server started with PID $serverPid." - -trap killServer EXIT - -# If --saucelabs option is passed, forward it to the protractor command adding -# the second argument that is required for local SauceLabs test run. -if [[ $1 = "--saucelabs" ]]; then - seleniumStarted=false - sleep 2 - echo "Using SauceLabs." - # $2 contains the tunnelIdentifier argument if specified, otherwise is empty. - evalModule protractor protractor.conf.js --saucelabs $2 -else - echo "Using Chrome and Firefox." - evalModule webdriver-manager clean - # Updates Selenium Webdriver. - evalModule webdriver-manager update - # Start Selenium Webdriver. - evalModule webdriver-manager start &>/dev/null & - seleniumStarted=true - echo "Selenium Server started." - # Wait for servers to come up. - sleep 10 - evalModule protractor protractor.conf.js -fi diff --git a/packages/auth/buildtools/sauce_connect.sh b/packages/auth/buildtools/sauce_connect.sh deleted file mode 100755 index a75d2671af9..00000000000 --- a/packages/auth/buildtools/sauce_connect.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -# Copyright 2016 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# Download and install SauceConnect under Linux 64-bit. To be used when testing -# with SauceLabs locally. See the instructions in protractor.conf.js file. -# -# It should not be used on Travis. Travis already handles SauceConnect. -# -# Script copied from the Closure Library repository: -# https://github.com/google/closure-library/blob/master/scripts/ci/sauce_connect.sh -# - -# Setup and start Sauce Connect locally. -CONNECT_URL="https://saucelabs.com/downloads/sc-4.4.1-linux.tar.gz" -CONNECT_DIR="/tmp/sauce-connect-$RANDOM" -CONNECT_DOWNLOAD="sc-latest-linux.tar.gz" - -BROWSER_PROVIDER_READY_FILE="/tmp/sauce-connect-ready" - -# Get Connect and start it. -mkdir -p $CONNECT_DIR -cd $CONNECT_DIR -curl $CONNECT_URL -o $CONNECT_DOWNLOAD 2> /dev/null 1> /dev/null -mkdir sauce-connect -tar --extract --file=$CONNECT_DOWNLOAD --strip-components=1 \ - --directory=sauce-connect > /dev/null -rm $CONNECT_DOWNLOAD - -function removeFiles() { - echo "Removing SauceConnect files..." - rm -rf $CONNECT_DIR -} - -trap removeFiles EXIT - -# This will be used by Protractor to connect to SauceConnect. -TUNNEL_IDENTIFIER="tunnelId-$RANDOM" -echo "" -echo "=========================================================================" -echo " Tunnel Identifier to pass to Protractor:" -echo " $TUNNEL_IDENTIFIER" -echo "=========================================================================" -echo "" -echo "" - -echo "Starting Sauce Connect..." - -# Start SauceConnect. -sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY \ - -i $TUNNEL_IDENTIFIER diff --git a/packages/auth/buildtools/test_template.html b/packages/auth/buildtools/test_template.html deleted file mode 100644 index 096a3f18aa1..00000000000 --- a/packages/auth/buildtools/test_template.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - $dom - - diff --git a/packages-exp/auth-exp/cordova/demo/.gitignore b/packages/auth/cordova/demo/.gitignore similarity index 100% rename from packages-exp/auth-exp/cordova/demo/.gitignore rename to packages/auth/cordova/demo/.gitignore diff --git a/packages-exp/auth-exp/cordova/demo/README.md b/packages/auth/cordova/demo/README.md similarity index 100% rename from packages-exp/auth-exp/cordova/demo/README.md rename to packages/auth/cordova/demo/README.md diff --git a/packages-exp/auth-exp/cordova/demo/package.json b/packages/auth/cordova/demo/package.json similarity index 87% rename from packages-exp/auth-exp/cordova/demo/package.json rename to packages/auth/cordova/demo/package.json index 02984d192f8..0c896d895f8 100644 --- a/packages-exp/auth-exp/cordova/demo/package.json +++ b/packages/auth/cordova/demo/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "scripts": { "build:js": "rollup -c", - "build:deps": "lerna run --scope @firebase/'{app-exp,auth-exp/cordova}' --include-dependencies build" + "build:deps": "lerna run --scope @firebase/'{app,auth/cordova}' --include-dependencies build" }, "keywords": [ "ecosystem:cordova" @@ -36,8 +36,8 @@ ] }, "dependencies": { - "@firebase/app-exp": "0.0.900", - "@firebase/auth-exp": "0.0.900", + "@firebase/app": "0.0.900", + "@firebase/auth": "0.0.900", "@firebase/logger": "0.2.6", "@firebase/util": "1.1.0", "cordova-plugin-browsertab": "0.2.0", diff --git a/packages-exp/auth-exp/cordova/demo/rollup.config.js b/packages/auth/cordova/demo/rollup.config.js similarity index 100% rename from packages-exp/auth-exp/cordova/demo/rollup.config.js rename to packages/auth/cordova/demo/rollup.config.js diff --git a/packages-exp/auth-exp/cordova/demo/sample-config.xml b/packages/auth/cordova/demo/sample-config.xml similarity index 100% rename from packages-exp/auth-exp/cordova/demo/sample-config.xml rename to packages/auth/cordova/demo/sample-config.xml diff --git a/packages-exp/auth-exp/cordova/demo/src/index.js b/packages/auth/cordova/demo/src/index.js similarity index 99% rename from packages-exp/auth-exp/cordova/demo/src/index.js rename to packages/auth/cordova/demo/src/index.js index c8bc760c31d..42014cbf7e4 100644 --- a/packages-exp/auth-exp/cordova/demo/src/index.js +++ b/packages/auth/cordova/demo/src/index.js @@ -22,7 +22,7 @@ * package. */ -import { initializeApp } from '@firebase/app-exp'; +import { initializeApp } from '@firebase/app'; import { applyActionCode, browserLocalPersistence, @@ -62,7 +62,7 @@ import { reauthenticateWithRedirect, getRedirectResult, cordovaPopupRedirectResolver -} from '@firebase/auth-exp/dist/cordova'; +} from '@firebase/auth/dist/cordova'; import { config } from './config'; import { diff --git a/packages-exp/auth-exp/cordova/demo/src/logging.js b/packages/auth/cordova/demo/src/logging.js similarity index 100% rename from packages-exp/auth-exp/cordova/demo/src/logging.js rename to packages/auth/cordova/demo/src/logging.js diff --git a/packages-exp/auth-exp/cordova/demo/src/sample-config.js b/packages/auth/cordova/demo/src/sample-config.js similarity index 100% rename from packages-exp/auth-exp/cordova/demo/src/sample-config.js rename to packages/auth/cordova/demo/src/sample-config.js diff --git a/packages-exp/auth-exp/cordova/demo/www/index.html b/packages/auth/cordova/demo/www/index.html similarity index 100% rename from packages-exp/auth-exp/cordova/demo/www/index.html rename to packages/auth/cordova/demo/www/index.html diff --git a/packages-exp/auth-exp/cordova/demo/www/style.css b/packages/auth/cordova/demo/www/style.css similarity index 100% rename from packages-exp/auth-exp/cordova/demo/www/style.css rename to packages/auth/cordova/demo/www/style.css diff --git a/packages-exp/auth-exp/cordova/package.json b/packages/auth/cordova/package.json similarity index 68% rename from packages-exp/auth-exp/cordova/package.json rename to packages/auth/cordova/package.json index 64d433f737e..b133d280d44 100644 --- a/packages-exp/auth-exp/cordova/package.json +++ b/packages/auth/cordova/package.json @@ -1,6 +1,7 @@ { - "name": "@firebase/auth-exp/cordova", + "name": "@firebase/auth/cordova", "description": "A Cordova-specific build of the Firebase Auth JS SDK", "browser": "../dist/cordova/index.js", + "module": "../dist/cordova/index.js", "typings": "../dist/cordova/index.cordova.d.ts" } \ No newline at end of file diff --git a/packages-exp/auth-exp/demo/.eslintignore b/packages/auth/demo/.eslintignore similarity index 100% rename from packages-exp/auth-exp/demo/.eslintignore rename to packages/auth/demo/.eslintignore diff --git a/packages-exp/auth-exp/demo/.eslintrc.js b/packages/auth/demo/.eslintrc.js similarity index 100% rename from packages-exp/auth-exp/demo/.eslintrc.js rename to packages/auth/demo/.eslintrc.js diff --git a/packages/auth/demo/.gitignore b/packages/auth/demo/.gitignore index dbb58ffbfa3..798f66cee47 100644 --- a/packages/auth/demo/.gitignore +++ b/packages/auth/demo/.gitignore @@ -1,66 +1,6 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -firebase-debug.log* -firebase-debug.*.log* - -# Firebase cache -.firebase/ - -# Firebase config - -# Uncomment this if you'd like others to create their own Firebase project. -# For a team working on the same Firebase project(s), it is recommended to leave -# it commented so all members can deploy to the same project(s) in .firebaserc. -# .firebaserc - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env +src/config.js +.firebaserc +.firebase +public/service-worker.* +public/web-worker.* +public/index.js* \ No newline at end of file diff --git a/packages/auth/demo/README.md b/packages/auth/demo/README.md index 12ff0f9ab58..a4d31ba955a 100644 --- a/packages/auth/demo/README.md +++ b/packages/auth/demo/README.md @@ -1,4 +1,4 @@ -# Firebase-Auth for web - Auth Demo +# Firebase-Auth for web - Auth Demo (Auth Next) ## Prerequisite @@ -30,10 +30,10 @@ firebase use --add Select the project you have created in the prerequisite, and type in `default` or any other name as the alias to use for this project. -Copy `public/sample-config.js` to `public/config.js`: +Copy `src/sample-config.js` to `src/config.js`: ```bash -cp public/sample-config.js public/config.js +cp src/sample-config.js src/config.js ``` Then copy and paste the Web snippet config found in the console (either by clicking "Add Firebase to @@ -42,40 +42,19 @@ in the `config.js` file. ## Deploy -### Option 1: Compile and use local Firebase Auth files - -To deploy the demo app, run the following command in the root directory of Firebase Auth (use `cd ..` -first if you are still in the `demo/` folder): - +Before deploying, you may need to build the auth package: ```bash -yarn run demo +yarn build:deps ``` -This will compile all the files needed to run Firebase Auth, and start a Firebase server locally at -[http://localhost:5000](http://localhost:5000). - -### Option 2: Use CDN hosted Firebase files +This can take some time, and you only need to do it if you've modified the auth package. -If you would prefer to use a CDN instead of locally compiled Firebase Auth files, you can instead -locate the following in the `` tag of `public/index.html`: - -```html - - - -``` - -Then replace that with the public CDN: - -```html - -``` - -Finally, ensure you are in the `demo/` folder (and not the root directory of Firebase Auth package), -and run: +To run the app locally, simply issue the following command in the `auth/demo` directory: ```bash yarn run demo ``` -This will start a Firebase server locally at [http://localhost:5000](http://localhost:5000). +This will compile all the files needed to run Firebase Auth, and start a Firebase server locally at +[http://localhost:5000](http://localhost:5000). + diff --git a/packages/auth/demo/firebase.json b/packages/auth/demo/firebase.json index c44bbb73ccf..300ada0abc9 100644 --- a/packages/auth/demo/firebase.json +++ b/packages/auth/demo/firebase.json @@ -4,28 +4,11 @@ }, "hosting": { "public": "public", - "rewrites": [ - { - "source": "/checkIfAuthenticated", - "function": "checkIfAuthenticated" - } - ] - }, - "emulators": { - "auth": { - "port": 9099 - }, - "functions": { - "port": 5001 - }, - "database": { - "port": 9000 - }, - "hosting": { - "port": 5000 - }, - "ui": { - "enabled": true - } + "rewrites": [ + { + "source": "/checkIfAuthenticated", + "function": "checkIfAuthenticated" + } + ] } } diff --git a/packages/auth/demo/functions/index.js b/packages/auth/demo/functions/index.js index 58ceaeb5f1a..f3a9091bf20 100644 --- a/packages/auth/demo/functions/index.js +++ b/packages/auth/demo/functions/index.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google Inc. + * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,14 +30,16 @@ exports.checkIfAuthenticated = functions.https.onRequest((req, res) => { const idToken = req.get('x-id-token'); res.setHeader('Content-Type', 'application/json'); if (idToken) { - admin.auth().verifyIdToken(idToken) - .then((decodedIdToken) => { - res.status(200).send(JSON.stringify({uid: decodedIdToken.sub})); - }) - .catch((error) => { - res.status(400).send(JSON.stringify({error: error.code})); - }); + admin + .auth() + .verifyIdToken(idToken) + .then(decodedIdToken => { + res.status(200).send(JSON.stringify({ uid: decodedIdToken.sub })); + }) + .catch(error => { + res.status(400).send(JSON.stringify({ error: error.code })); + }); } else { - res.status(403).send(JSON.stringify({error: 'Unauthorized access'})); + res.status(403).send(JSON.stringify({ error: 'Unauthorized access' })); } }); diff --git a/packages/auth/demo/functions/package.json b/packages/auth/demo/functions/package.json index 00dc1813e1e..c825a2e5203 100644 --- a/packages/auth/demo/functions/package.json +++ b/packages/auth/demo/functions/package.json @@ -9,8 +9,11 @@ "logs": "firebase functions:log" }, "dependencies": { - "firebase-admin": "9.9.0", + "firebase-admin": "8.13.0", "firebase-functions": "3.14.1" }, - "private": true + "private": true, + "engines": { + "node": "10" + } } diff --git a/packages/auth/demo/functions/yarn.lock b/packages/auth/demo/functions/yarn.lock index debe9e7e809..d0de26b87a5 100644 --- a/packages/auth/demo/functions/yarn.lock +++ b/packages/auth/demo/functions/yarn.lock @@ -2,133 +2,133 @@ # yarn lockfile v1 -"@firebase/app-types@0.6.2": - version "0.6.2" - resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.2.tgz#8578cb1061a83ced4570188be9e225d54e0f27fb" - integrity sha512-2VXvq/K+n8XMdM4L2xy5bYp2ZXMawJXluUIDzUBvMthVR+lhxK4pfFiqr1mmDbv9ydXvEAuFsD+6DpcZuJcSSw== +"@firebase/app-types@0.6.1": + version "0.6.1" + resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz#dcbd23030a71c0c74fc95d4a3f75ba81653850e9" + integrity sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg== -"@firebase/auth-interop-types@0.1.6": - version "0.1.6" - resolved "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz#5ce13fc1c527ad36f1bb1322c4492680a6cf4964" - integrity sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g== +"@firebase/auth-interop-types@0.1.5": + version "0.1.5" + resolved "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz#9fc9bd7c879f16b8d1bb08373a0f48c3a8b74557" + integrity sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw== -"@firebase/component@0.5.3": - version "0.5.3" - resolved "https://registry.npmjs.org/@firebase/component/-/component-0.5.3.tgz#1ccd4d0814f9c1d7f179deab2122374f74571315" - integrity sha512-/TzwmlK35Mnr31zA9D4X0Obln7waAtV7nDLuNVtWhlXl0sSYRxnGES4dOhSXi0yWRneaNr+OiRBZ2gsc9PWWRg== +"@firebase/component@0.1.19": + version "0.1.19" + resolved "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz#bd2ac601652c22576b574c08c40da245933dbac7" + integrity sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ== dependencies: - "@firebase/util" "1.1.0" - tslib "^2.1.0" + "@firebase/util" "0.3.2" + tslib "^1.11.1" -"@firebase/database-types@0.7.2", "@firebase/database-types@^0.7.2": - version "0.7.2" - resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.2.tgz#449c4b36ec59a1ad9089797b540e2ba1c0d4fcbf" - integrity sha512-cdAd/dgwvC0r3oLEDUR+ULs1vBsEvy0b27nlzKhU6LQgm9fCDzgaH9nFGv8x+S9dly4B0egAXkONkVoWcOAisg== +"@firebase/database-types@0.5.2": + version "0.5.2" + resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz#23bec8477f84f519727f165c687761e29958b63c" + integrity sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g== dependencies: - "@firebase/app-types" "0.6.2" + "@firebase/app-types" "0.6.1" -"@firebase/database@^0.10.0": - version "0.10.5" - resolved "https://registry.npmjs.org/@firebase/database/-/database-0.10.5.tgz#99de469642768766fdefcc560d04a091d1390de2" - integrity sha512-/KAFZGSvvL3J4EytZsl5kgqhZwEV+ZTz6mCS3VPigkkECzT1E/JRm9h8DY5/VWmoyfqc5O2F3kqrrLf7AovoHg== +"@firebase/database@^0.6.0": + version "0.6.13" + resolved "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz#b96fe0c53757dd6404ee085fdcb45c0f9f525c17" + integrity sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA== dependencies: - "@firebase/auth-interop-types" "0.1.6" - "@firebase/component" "0.5.3" - "@firebase/database-types" "0.7.2" + "@firebase/auth-interop-types" "0.1.5" + "@firebase/component" "0.1.19" + "@firebase/database-types" "0.5.2" "@firebase/logger" "0.2.6" - "@firebase/util" "1.1.0" + "@firebase/util" "0.3.2" faye-websocket "0.11.3" - tslib "^2.1.0" + tslib "^1.11.1" "@firebase/logger@0.2.6": version "0.2.6" resolved "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz#3aa2ca4fe10327cabf7808bd3994e88db26d7989" integrity sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw== -"@firebase/util@1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@firebase/util/-/util-1.1.0.tgz#add2d57d0b2307a932520abdee303b66be0ac8b0" - integrity sha512-lfuSASuPKNdfebuFR8rjFamMQUPH9iiZHcKS755Rkm/5gRT0qC7BMhCh3ZkHf7NVbplzIc/GhmX2jM+igDRCag== +"@firebase/util@0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz#87de27f9cffc2324651cabf6ec133d0a9eb21b52" + integrity sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g== dependencies: - tslib "^2.1.0" + tslib "^1.11.1" -"@google-cloud/common@^3.6.0": - version "3.6.0" - resolved "https://registry.npmjs.org/@google-cloud/common/-/common-3.6.0.tgz#c2f6da5f79279a4a9ac7c71fc02d582beab98e8b" - integrity sha512-aHIFTqJZmeTNO9md8XxV+ywuvXF3xBm5WNmgWeeCK+XN5X+kGW0WEX94wGwj+/MdOnrVf4dL2RvSIt9J5yJG6Q== +"@google-cloud/common@^2.1.1": + version "2.4.0" + resolved "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz#2783b7de8435024a31453510f2dab5a6a91a4c82" + integrity sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg== dependencies: - "@google-cloud/projectify" "^2.0.0" - "@google-cloud/promisify" "^2.0.0" - arrify "^2.0.1" - duplexify "^4.1.1" + "@google-cloud/projectify" "^1.0.0" + "@google-cloud/promisify" "^1.0.0" + arrify "^2.0.0" + duplexify "^3.6.0" ent "^2.2.0" extend "^3.0.2" - google-auth-library "^7.0.2" - retry-request "^4.1.1" - teeny-request "^7.0.0" + google-auth-library "^5.5.0" + retry-request "^4.0.0" + teeny-request "^6.0.0" -"@google-cloud/firestore@^4.5.0": - version "4.9.7" - resolved "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.9.7.tgz#8fb9080ba0f6e074013412835b60db926515d139" - integrity sha512-s5W6rRxD5y3Oe3KJUNztIy4eIi9dBwJU36jd/QM3L8frpCuSh1fn6z0BD8IAV0AirQAg6aOzSlcwAwd/yeXCkw== +"@google-cloud/firestore@^3.0.0": + version "3.8.6" + resolved "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.8.6.tgz#9e6dea57323a5824563430a759244825fb01d834" + integrity sha512-ox80NbrM1MLJgvAAUd1quFLx/ie/nSjrk1PtscSicpoYDlKb9e6j7pHrVpbopBMyliyfNl3tLJWaDh+x+uCXqw== dependencies: - fast-deep-equal "^3.1.1" + deep-equal "^2.0.0" functional-red-black-tree "^1.0.1" - google-gax "^2.9.2" - protobufjs "^6.8.6" + google-gax "^1.15.3" + readable-stream "^3.4.0" + through2 "^3.0.0" -"@google-cloud/paginator@^3.0.0": - version "3.0.5" - resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz#9d6b96c421a89bd560c1bc2c197c7611ef21db6c" - integrity sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw== +"@google-cloud/paginator@^2.0.0": + version "2.0.3" + resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz#c7987ad05d1c3ebcef554381be80e9e8da4e4882" + integrity sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg== dependencies: arrify "^2.0.0" extend "^3.0.2" -"@google-cloud/projectify@^2.0.0": - version "2.0.1" - resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz#13350ee609346435c795bbfe133a08dfeab78d65" - integrity sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ== +"@google-cloud/projectify@^1.0.0": + version "1.0.4" + resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz#28daabebba6579ed998edcadf1a8f3be17f3b5f0" + integrity sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg== -"@google-cloud/promisify@^2.0.0": - version "2.0.3" - resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz#f934b5cdc939e3c7039ff62b9caaf59a9d89e3a8" - integrity sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw== +"@google-cloud/promisify@^1.0.0": + version "1.0.4" + resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz#ce86ffa94f9cfafa2e68f7b3e4a7fad194189723" + integrity sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ== -"@google-cloud/storage@^5.3.0": - version "5.8.1" - resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.8.1.tgz#00e627723614bcf97e6e29f9a59ec39339171847" - integrity sha512-qP8gCJ2myyMN3JMJN12d82Oo8VBSDO8vO4/x56dtQZX9+WISqcagurntnJVyFX885tIOtS97bsyv8qR1xv6HMg== +"@google-cloud/storage@^4.1.2": + version "4.7.0" + resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz#a7466086a83911c7979cc238d00a127ffb645615" + integrity sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ== dependencies: - "@google-cloud/common" "^3.6.0" - "@google-cloud/paginator" "^3.0.0" - "@google-cloud/promisify" "^2.0.0" + "@google-cloud/common" "^2.1.1" + "@google-cloud/paginator" "^2.0.0" + "@google-cloud/promisify" "^1.0.0" arrify "^2.0.0" - async-retry "^1.3.1" compressible "^2.0.12" - date-and-time "^0.14.2" - duplexify "^4.0.0" + concat-stream "^2.0.0" + date-and-time "^0.13.0" + duplexify "^3.5.0" extend "^3.0.2" - gaxios "^4.0.0" - gcs-resumable-upload "^3.1.3" - get-stream "^6.0.0" + gaxios "^3.0.0" + gcs-resumable-upload "^2.2.4" hash-stream-validation "^0.2.2" mime "^2.2.0" mime-types "^2.0.8" onetime "^5.1.0" - p-limit "^3.0.1" + p-limit "^2.2.0" pumpify "^2.0.0" + readable-stream "^3.4.0" snakeize "^0.1.0" stream-events "^1.0.1" + through2 "^3.0.0" xdg-basedir "^4.0.0" -"@grpc/grpc-js@~1.2.0": - version "1.2.10" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.2.10.tgz#f316d29a45fcc324e923d593cb849d292b1ed598" - integrity sha512-wj6GkNiorWYaPiIZ767xImmw7avMMVUweTvPFg4mJWOxz2180DKwfuxhJJZ7rpc1+7D3mX/v8vJdxTuIo71Ieg== +"@grpc/grpc-js@~1.0.3": + version "1.0.5" + resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz#09948c0810e62828fdd61455b2eb13d7879888b0" + integrity sha512-Hm+xOiqAhcpT9RYM8lc15dbQD7aQurM7ZU8ulmulepiPlN7iwBXXwP3vSBUimoFoApRqz7pSIisXU8pZaCB4og== dependencies: - "@types/node" ">=12.12.47" - google-auth-library "^6.1.1" semver "^6.2.0" "@grpc/proto-loader@^0.5.1": @@ -139,11 +139,6 @@ lodash.camelcase "^4.3.0" protobufjs "^6.8.6" -"@panva/asn1.js@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" - integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== - "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -217,14 +212,6 @@ dependencies: "@types/node" "*" -"@types/express-jwt@0.0.42": - version "0.0.42" - resolved "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz#4f04e1fadf9d18725950dc041808a4a4adf7f5ae" - integrity sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag== - dependencies: - "@types/express" "*" - "@types/express-unless" "*" - "@types/express-serve-static-core@*": version "4.17.18" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" @@ -234,32 +221,6 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express-serve-static-core@^4.17.18": - version "4.17.21" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz#a427278e106bca77b83ad85221eae709a3414d42" - integrity sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express-unless@*": - version "0.5.1" - resolved "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz#4f440b905e42bbf53382b8207bc337dc5ff9fd1f" - integrity sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw== - dependencies: - "@types/express" "*" - -"@types/express@*": - version "4.17.12" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz#4bc1bf3cd0cfe6d3f6f2853648b40db7d54de350" - integrity sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - "@types/express@4.17.3": version "4.17.3" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9" @@ -269,6 +230,13 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" +"@types/fs-extra@^8.0.1": + version "8.1.1" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" + integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== + dependencies: + "@types/node" "*" + "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -279,7 +247,7 @@ resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/node@*", "@types/node@>=12.12.47": +"@types/node@*": version "14.14.33" resolved "https://registry.npmjs.org/@types/node/-/node-14.14.33.tgz#9e4f8c64345522e4e8ce77b334a8aaa64e2b6c78" integrity sha512-oJqcTrgPUF29oUP8AsUqbXGJNuPutsetaa9kTQAQce5Lx5dTYWV02ScBiT/k1BX/Z7pKeqedmvp39Wu4zR7N7g== @@ -289,6 +257,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-13.13.46.tgz#5471e176f3fa15e018dea7992072bf8ca208a3a6" integrity sha512-dqpbzK/KDsOlEt+oyB3rv+u1IxlLFziZu/Z0adfRKoelkr+sTd6QcgiQC+HWq/vkYkHwG5ot2LxgV05aAjnhcg== +"@types/node@^8.10.59": + version "8.10.66" + resolved "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" + integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== + "@types/qs@*": version "6.9.6" resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" @@ -329,22 +302,27 @@ agent-base@6: dependencies: debug "4" +array-filter@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" + integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= -arrify@^2.0.0, arrify@^2.0.1: +arrify@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -async-retry@^1.3.1: - version "1.3.1" - resolved "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55" - integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA== +available-typed-arrays@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" + integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== dependencies: - retry "0.12.0" + array-filter "^1.0.0" base64-js@^1.3.0: version "1.5.1" @@ -377,11 +355,24 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + bytes@3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + compressible@^2.0.12: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -389,6 +380,16 @@ compressible@^2.0.12: dependencies: mime-db ">= 1.43.0 < 2" +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + configstore@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -423,6 +424,11 @@ cookie@0.4.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + cors@^2.8.5: version "2.8.5" resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" @@ -436,10 +442,10 @@ crypto-random-string@^2.0.0: resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -date-and-time@^0.14.2: - version "0.14.2" - resolved "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.2.tgz#a4266c3dead460f6c231fe9674e585908dac354e" - integrity sha512-EFTCh9zRSEpGPmJaexg7HTuzZHh6cnJj1ui7IGCFNXzd2QdpsNh05Db5TF3xzJm30YN+A8/6xHSuRcQqoc3kFA== +date-and-time@^0.13.0: + version "0.13.1" + resolved "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz#d12ba07ac840d5b112dc4c83f8a03e8a51f78dd6" + integrity sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw== debug@2.6.9: version "2.6.9" @@ -448,13 +454,41 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.1.1: version "4.3.1" resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" +deep-equal@^2.0.0: + version "2.0.5" + resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" + integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw== + dependencies: + call-bind "^1.0.0" + es-get-iterator "^1.1.1" + get-intrinsic "^1.0.1" + is-arguments "^1.0.4" + is-date-object "^1.0.2" + is-regex "^1.1.1" + isarray "^2.0.5" + object-is "^1.1.4" + object-keys "^1.1.1" + object.assign "^4.1.2" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.3" + which-boxed-primitive "^1.0.1" + which-collection "^1.0.1" + which-typed-array "^1.1.2" + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -479,7 +513,17 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -duplexify@^4.0.0, duplexify@^4.1.1: +duplexify@^3.5.0, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +duplexify@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz#7027dc374f157b122a8ae08c2d3ea4d2d953aa61" integrity sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA== @@ -506,7 +550,7 @@ encodeurl@~1.0.2: resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -518,6 +562,51 @@ ent@^2.2.0: resolved "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= +es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: + version "1.18.0" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" + +es-get-iterator@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" + integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.0" + has-symbols "^1.0.1" + is-arguments "^1.1.0" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.5" + isarray "^2.0.5" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -574,12 +663,7 @@ extend@^3.0.2: resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3: +fast-text-encoding@^1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== @@ -604,21 +688,19 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -firebase-admin@9.9.0: - version "9.9.0" - resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.9.0.tgz#40442704b5ac0fddfdcdf4255601f8acb1e6fab3" - integrity sha512-04HT7JAAqcJYty95qf15IBD9CXf+vr7S8zNU6Zt1ayC1J05DLaCsUd19/sCNAjZ614KHexAYUtyLgZoJwu2wOQ== +firebase-admin@8.13.0: + version "8.13.0" + resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.13.0.tgz#997d34ae8357d7dc162ba622148bbebcf7f2e923" + integrity sha512-krXj5ncWMJBhCpXSn9UFY6zmDWjFjqgx+1e9ATXKFYndEjmKtNBuJzqdrAdDh7aTUR7X6+0TPx4Hbc08kd0lwQ== dependencies: - "@firebase/database" "^0.10.0" - "@firebase/database-types" "^0.7.2" - "@types/node" ">=12.12.47" + "@firebase/database" "^0.6.0" + "@types/node" "^8.10.59" dicer "^0.3.0" jsonwebtoken "^8.5.1" - jwks-rsa "^2.0.2" - node-forge "^0.10.0" + node-forge "^0.7.6" optionalDependencies: - "@google-cloud/firestore" "^4.5.0" - "@google-cloud/storage" "^5.3.0" + "@google-cloud/firestore" "^3.0.0" + "@google-cloud/storage" "^4.1.2" firebase-functions@3.14.1: version "3.14.1" @@ -630,6 +712,11 @@ firebase-functions@3.14.1: express "^4.17.1" lodash "^4.17.14" +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -640,15 +727,31 @@ fresh@0.5.2: resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gaxios@^4.0.0: - version "4.2.0" - resolved "https://registry.npmjs.org/gaxios/-/gaxios-4.2.0.tgz#33bdc4fc241fc33b8915a4b8c07cfb368b932e46" - integrity sha512-Ms7fNifGv0XVU+6eIyL9LB7RVESeML9+cMvkwGS70xyD6w2Z80wl6RiqiJ9k1KFlJCUTQqFFc8tXmPQfSKUe8g== +gaxios@^2.0.0, gaxios@^2.1.0: + version "2.3.4" + resolved "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz#eea99353f341c270c5f3c29fc46b8ead56f0a173" + integrity sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA== + dependencies: + abort-controller "^3.0.0" + extend "^3.0.2" + https-proxy-agent "^5.0.0" + is-stream "^2.0.0" + node-fetch "^2.3.0" + +gaxios@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz#11b6f0e8fb08d94a10d4d58b044ad3bec6dd486a" + integrity sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q== dependencies: abort-controller "^3.0.0" extend "^3.0.2" @@ -656,99 +759,109 @@ gaxios@^4.0.0: is-stream "^2.0.0" node-fetch "^2.3.0" -gcp-metadata@^4.2.0: - version "4.2.1" - resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz#31849fbcf9025ef34c2297c32a89a1e7e9f2cd62" - integrity sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw== +gcp-metadata@^3.4.0: + version "3.5.0" + resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz#6d28343f65a6bbf8449886a0c0e4a71c77577055" + integrity sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA== dependencies: - gaxios "^4.0.0" - json-bigint "^1.0.0" + gaxios "^2.1.0" + json-bigint "^0.3.0" -gcs-resumable-upload@^3.1.3: - version "3.1.3" - resolved "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.3.tgz#1e38e1339600b85812e6430a5ab455453c64cce3" - integrity sha512-LjVrv6YVH0XqBr/iBW0JgRA1ndxhK6zfEFFJR4im51QVTj/4sInOXimY2evDZuSZ75D3bHxTaQAdXRukMc1y+w== +gcs-resumable-upload@^2.2.4: + version "2.3.3" + resolved "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz#02c616ed17eff6676e789910aeab3907d412c5f8" + integrity sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q== dependencies: abort-controller "^3.0.0" configstore "^5.0.0" - extend "^3.0.2" - gaxios "^4.0.0" - google-auth-library "^7.0.0" + gaxios "^2.0.0" + google-auth-library "^5.0.0" pumpify "^2.0.0" stream-events "^1.0.4" -get-stream@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" - integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== - -google-auth-library@^6.1.1: - version "6.1.6" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz#deacdcdb883d9ed6bac78bb5d79a078877fdf572" - integrity sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ== +get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== 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" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" -google-auth-library@^7.0.0, google-auth-library@^7.0.2: - version "7.0.2" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.0.2.tgz#cab6fc7f94ebecc97be6133d6519d9946ccf3e9d" - integrity sha512-vjyNZR3pDLC0u7GHLfj+Hw9tGprrJwoMwkYGqURCXYITjCrP9HprOyxVV+KekdLgATtWGuDkQG2MTh0qpUPUgg== +google-auth-library@^5.0.0, google-auth-library@^5.5.0: + version "5.10.1" + resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz#504ec75487ad140e68dd577c21affa363c87ddff" + integrity sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg== 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" + gaxios "^2.1.0" + gcp-metadata "^3.4.0" + gtoken "^4.1.0" jws "^4.0.0" - lru-cache "^6.0.0" + lru-cache "^5.0.0" -google-gax@^2.9.2: - version "2.11.0" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-2.11.0.tgz#7219de6558747680489985de1bf769e46ec0771c" - integrity sha512-aZMjCMw4wVssxplkO9Pq77zCHiYecvTYY51HeKOti0LLHaNrRo96F8h8NIA4hcWeh2u6COFX3YFmZyxrARXlGA== +google-gax@^1.15.3: + version "1.15.3" + resolved "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz#e88cdcbbd19c7d88cc5fd7d7b932c4d1979a5aca" + integrity sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ== dependencies: - "@grpc/grpc-js" "~1.2.0" + "@grpc/grpc-js" "~1.0.3" "@grpc/proto-loader" "^0.5.1" + "@types/fs-extra" "^8.0.1" "@types/long" "^4.0.0" abort-controller "^3.0.0" - duplexify "^4.0.0" - fast-text-encoding "^1.0.3" - google-auth-library "^7.0.2" + duplexify "^3.6.0" + google-auth-library "^5.0.0" is-stream-ended "^0.1.4" - node-fetch "^2.6.1" - protobufjs "^6.10.2" + lodash.at "^4.6.0" + lodash.has "^4.5.2" + node-fetch "^2.6.0" + protobufjs "^6.8.9" retry-request "^4.0.0" + semver "^6.0.0" + walkdir "^0.4.0" -google-p12-pem@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz#673ac3a75d3903a87f05878f3c75e06fc151669e" - integrity sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA== +google-p12-pem@^2.0.0: + version "2.0.4" + resolved "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz#036462394e266472632a78b685f0cc3df4ef337b" + integrity sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg== dependencies: - node-forge "^0.10.0" + node-forge "^0.9.0" graceful-fs@^4.1.2: version "4.2.6" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -gtoken@^5.0.4: - version "5.2.1" - resolved "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz#4dae1fea17270f457954b4a45234bba5fc796d16" - integrity sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw== +gtoken@^4.1.0: + version "4.1.4" + resolved "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz#925ff1e7df3aaada06611d30ea2d2abf60fcd6a7" + integrity sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA== dependencies: - gaxios "^4.0.0" - google-p12-pem "^3.0.3" + gaxios "^2.1.0" + google-p12-pem "^2.0.0" jws "^4.0.0" + mime "^2.2.0" + +has-bigints@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" hash-stream-validation@^0.2.2: version "0.2.4" @@ -816,7 +929,7 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -inherits@2.0.4, inherits@^2.0.3: +inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -826,11 +939,68 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-arguments@^1.0.4, is-arguments@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" + integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== + +is-boolean-object@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" + integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + dependencies: + call-bind "^1.0.0" + +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== + +is-date-object@^1.0.1, is-date-object@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number-object@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + is-obj@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== +is-regex@^1.1.1, is-regex@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" + integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.1" + +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== + is-stream-ended@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz#f50224e95e06bce0e356d440a4827cd35b267eda" @@ -841,22 +1011,58 @@ is-stream@^2.0.0: resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typed-array@^1.1.3: + version "1.1.5" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" + integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== + dependencies: + available-typed-arrays "^1.0.2" + call-bind "^1.0.2" + es-abstract "^1.18.0-next.2" + foreach "^2.0.5" + has-symbols "^1.0.1" + is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -jose@^2.0.5: +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + +is-weakset@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" + integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== + +isarray@^2.0.5: version "2.0.5" - resolved "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz#29746a18d9fff7dcf9d5d2a6f62cb0c7cd27abd3" - integrity sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA== - dependencies: - "@panva/asn1.js" "^1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== -json-bigint@^1.0.0: +isarray@~1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" - integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +json-bigint@^0.3.0: + version "0.3.1" + resolved "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.1.tgz#0c1729d679f580d550899d6a2226c228564afe60" + integrity sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ== dependencies: bignumber.js "^9.0.0" @@ -894,17 +1100,6 @@ jwa@^2.0.0: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jwks-rsa@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.3.tgz#4059f25e27f1d9cb5681dd12a98e46f8aa39fcbd" - integrity sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg== - dependencies: - "@types/express-jwt" "0.0.42" - debug "^4.1.0" - jose "^2.0.5" - limiter "^1.1.5" - lru-memoizer "^2.1.2" - jws@^3.2.2: version "3.2.2" resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" @@ -921,20 +1116,20 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" -limiter@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" - integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== +lodash.at@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz#93cdce664f0a1994ea33dd7cd40e23afd11b0ff8" + integrity sha1-k83OZk8KGZTqM9181A4jr9EbD/g= lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.has@^4.5.2: + version "4.5.2" + resolved "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= lodash.includes@^4.3.0: version "4.3.0" @@ -981,28 +1176,12 @@ long@^4.0.0: resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== +lru-cache@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: - yallist "^4.0.0" - -lru-cache@~4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" - integrity sha1-HRdnnAac2l0ECZGgnbwsDbN35V4= - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" - -lru-memoizer@^2.1.2: - version "2.1.4" - resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz#b864d92b557f00b1eeb322156a0409cb06dafac6" - integrity sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ== - dependencies: - lodash.clonedeep "^4.5.0" - lru-cache "~4.0.0" + yallist "^3.0.2" make-dir@^3.0.0: version "3.1.0" @@ -1078,21 +1257,54 @@ negotiator@0.6.2: resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -node-fetch@^2.3.0, node-fetch@^2.6.1: +node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0: version "2.6.1" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^0.7.6: + version "0.7.6" + resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + +node-forge@^0.9.0: + version "0.9.2" + resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz#b35a44c28889b2ea55cabf8c79e3563f9676190a" + integrity sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw== object-assign@^4: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + +object-is@^1.1.4: + version "1.1.5" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -1114,12 +1326,17 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -p-limit@^3.0.1: - version "3.1.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: - yocto-queue "^0.1.0" + p-try "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== parseurl@~1.3.3: version "1.3.3" @@ -1131,7 +1348,12 @@ path-to-regexp@0.1.7: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -protobufjs@^6.10.2, protobufjs@^6.8.6: +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +protobufjs@^6.8.6, protobufjs@^6.8.9: version "6.10.2" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ== @@ -1158,11 +1380,6 @@ proxy-addr@~2.0.5: forwarded "~0.1.2" ipaddr.js "1.9.1" -pseudomap@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= - pump@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -1200,7 +1417,7 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -readable-stream@^3.1.1: +"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.0" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -1209,19 +1426,35 @@ readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" -retry-request@^4.0.0, retry-request@^4.1.1: +readable-stream@^2.0.0: + version "2.3.7" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +regexp.prototype.flags@^1.3.0: + version "1.3.1" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +retry-request@^4.0.0: version "4.1.3" resolved "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz#d5f74daf261372cff58d08b0a1979b4d7cab0fde" integrity sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ== dependencies: debug "^4.1.1" -retry@0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - -safe-buffer@5.1.2: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -1280,6 +1513,15 @@ setprototypeof@1.1.1: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +side-channel@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -1312,6 +1554,22 @@ streamsearch@0.1.2: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -1319,31 +1577,46 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + stubs@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls= -teeny-request@^7.0.0: - version "7.0.1" - resolved "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz#bdd41fdffea5f8fbc0d29392cb47bec4f66b2b4c" - integrity sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw== +teeny-request@^6.0.0: + version "6.0.3" + resolved "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz#b617f9d5b7ba95c76a3f257f6ba2342b70228b1f" + integrity sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw== dependencies: http-proxy-agent "^4.0.0" https-proxy-agent "^5.0.0" - node-fetch "^2.6.1" + node-fetch "^2.2.0" stream-events "^1.0.5" - uuid "^8.0.0" + uuid "^7.0.0" + +through2@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" + integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ== + dependencies: + inherits "^2.0.4" + readable-stream "2 || 3" toidentifier@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -tslib@^2.1.0: - version "2.3.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" - integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== +tslib@^1.11.1: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" @@ -1360,6 +1633,21 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +unbox-primitive@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f" + integrity sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.0" + has-symbols "^1.0.0" + which-boxed-primitive "^1.0.1" + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -1372,7 +1660,7 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -1382,16 +1670,21 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^8.0.0: - version "8.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^7.0.0: + version "7.0.3" + resolved "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" + integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +walkdir@^0.4.0: + version "0.4.1" + resolved "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" + integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== + websocket-driver@>=0.5.1: version "0.7.4" resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" @@ -1406,6 +1699,40 @@ websocket-extensions@>=0.1.1: resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +which-boxed-primitive@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-collection@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + +which-typed-array@^1.1.2: + version "1.1.4" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" + integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== + dependencies: + available-typed-arrays "^1.0.2" + call-bind "^1.0.0" + es-abstract "^1.18.0-next.1" + foreach "^2.0.5" + function-bind "^1.1.1" + has-symbols "^1.0.1" + is-typed-array "^1.1.3" + wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -1426,17 +1753,7 @@ xdg-basedir@^4.0.0: resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== -yallist@^2.0.0: - version "2.1.2" - resolved "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/packages-exp/auth-exp/demo/package.json b/packages/auth/demo/package.json similarity index 84% rename from packages-exp/auth-exp/demo/package.json rename to packages/auth/demo/package.json index 60b249ec504..6901760b74e 100644 --- a/packages-exp/auth-exp/demo/package.json +++ b/packages/auth/demo/package.json @@ -1,5 +1,5 @@ { - "name": "@firebase/auth-exp-demo", + "name": "@firebase/auth-demo", "version": "0.1.0", "private": true, "description": "Demo for Auth TS SDK", @@ -12,12 +12,12 @@ "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../../.gitignore'", "demo": "rollup -c && firebase serve", "build": "rollup -c", - "build:deps": "lerna run --scope @firebase/'{app-exp,auth-exp}' --include-dependencies build", + "build:deps": "lerna run --scope @firebase/'{app,auth}' --include-dependencies build", "dev": "rollup -c -w" }, "dependencies": { - "@firebase/app-exp": "0.0.900", - "@firebase/auth-exp": "0.0.900", + "@firebase/app": "0.0.900", + "@firebase/auth": "0.0.900", "@firebase/logger": "0.2.6", "@firebase/util": "1.1.0", "tslib": "^2.1.0" @@ -35,7 +35,7 @@ "lerna": "4.0.0" }, "repository": { - "directory": "packages-exp/auth-exp/demo", + "directory": "packages/auth/demo", "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, diff --git a/packages/auth/demo/public/common.js b/packages/auth/demo/public/common.js index d5def6f2290..9224926af95 100644 --- a/packages/auth/demo/public/common.js +++ b/packages/auth/demo/public/common.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google Inc. + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ * @fileoverview Utilities for Auth test app features. */ - /** * Initializes the widget for toggling reCAPTCHA size. * @param {function(string):void} callback The callback to call when the @@ -28,7 +27,7 @@ function initRecaptchaToggle(callback) { // Listen to recaptcha config togglers. var $recaptchaConfigTogglers = $('.toggleRecaptcha'); - $recaptchaConfigTogglers.click(function(e) { + $recaptchaConfigTogglers.click(function (e) { // Remove currently active option. $recaptchaConfigTogglers.removeClass('active'); // Set currently selected option. @@ -41,14 +40,16 @@ function initRecaptchaToggle(callback) { // Install servicerWorker if supported. if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/service-worker.js', {scope: '/'}) - .then(function(reg) { - // Registration worked. - console.log('Registration succeeded. Scope is ' + reg.scope); - }).catch(function(error) { - // Registration failed. - console.log('Registration failed with ' + error.message); - }); + navigator.serviceWorker + .register('/service-worker.js', { scope: '/' }) + .then(function (reg) { + // Registration worked. + console.log('Registration succeeded. Scope is ' + reg.scope); + }) + .catch(function (error) { + // Registration failed. + console.log('Registration failed with ' + error.message); + }); } var webWorker = null; @@ -58,12 +59,13 @@ if (window.Worker) { * Handles the incoming message from the web worker. * @param {!Object} e The message event received. */ - webWorker.onmessage = function(e) { - console.log('User data passed through web worker: ', e.data); + webWorker.onmessage = function (e) { + console.log('User data passed through web worker: ', e.data); switch (e.data.type) { case 'GET_USER_INFO': alertSuccess( - 'User data passed through web worker: ' + JSON.stringify(e.data)); + 'User data passed through web worker: ' + JSON.stringify(e.data) + ); break; case 'RUN_TESTS': if (e.data.status == 'success') { @@ -84,7 +86,7 @@ if (window.Worker) { */ function onGetCurrentUserDataFromWebWorker() { if (webWorker) { - webWorker.postMessage({type: 'GET_USER_INFO'}); + webWorker.postMessage({ type: 'GET_USER_INFO' }); } else { alertError('Error: Web workers are not supported in the current browser!'); } diff --git a/packages/auth/demo/public/index.html b/packages/auth/demo/public/index.html index 3d518d16854..aa32e144ed5 100644 --- a/packages/auth/demo/public/index.html +++ b/packages/auth/demo/public/index.html @@ -9,12 +9,7 @@ - - - - - - + Select a second factor to sign in with + diff --git a/packages/auth/demo/public/sample-config.js b/packages/auth/demo/public/sample-config.js deleted file mode 100644 index 24269faf4dc..00000000000 --- a/packages/auth/demo/public/sample-config.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * @license - * Copyright 2017 Google LLC All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. - */ - -var config = { - apiKey: "YOUR_API_KEY", - authDomain: "your-app.firebaseapp.com", - databaseURL: "https://your-app.firebaseio.com", - projectId: "your-app", - storageBucket: "your-app.appspot.com", - messagingSenderId: "MESSAGING_SENDER_ID" -}; - -// Uncomment the following line to use with emulator -// var emulatorUrl = 'http://localhost:9099'; \ No newline at end of file diff --git a/packages/auth/demo/public/script.js b/packages/auth/demo/public/script.js deleted file mode 100644 index 99ba50cd730..00000000000 --- a/packages/auth/demo/public/script.js +++ /dev/null @@ -1,1822 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Common javascript for the application. - */ - -var app = null; -var auth = null; -var tempApp = null; -var tempAuth = null; -var currentTab = null; -var lastUser = null; -var applicationVerifier = null; -var multiFactorErrorResolver = null; -var selectedMultiFactorHint = null; -var recaptchaSize = 'normal'; - -// Fix for IE8 when developer's console is not opened. -if (!window.console) { - window.console = { - log: function() {}, - error: function() {} - }; -} - - -// The corresponding Font Awesome icons for each provider. -var providersIcons = { - 'google.com': 'fa-google', - 'facebook.com': 'fa-facebook-official', - 'twitter.com': 'fa-twitter-square', - 'github.com': 'fa-github', - 'yahoo.com': 'fa-yahoo', - 'phone': 'fa-phone' -}; - - -/** - * Logs the message in the console and on the log window in the app - * using the level given. - * @param {?Object} message Object or message to log. - * @param {string} level The level of log (log, error, debug). - * @private - */ -function logAtLevel_(message, level) { - if (message != null) { - var messageDiv = $('
'); - messageDiv.addClass(level); - if (typeof message === 'object') { - messageDiv.text(JSON.stringify(message, null, ' ')); - } else { - messageDiv.text(message); - } - $('.logs').append(messageDiv); - } - console[level](message); -} - - -/** - * Logs info level. - * @param {string} message Object or message to log. - */ -function log(message) { - logAtLevel_(message, 'log'); -} - -/** - * Clear the logs. - */ -function clearLogs() { - $('.logs').text(''); -} - - -/** - * Displays for a few seconds a box with a specific message and then fades - * it out. - * @param {string} message Small message to display. - * @param {string} cssClass The class(s) to give the alert box. - * @private - */ -function alertMessage_(message, cssClass) { - var alertBox = $('
') - .addClass(cssClass) - .css('display', 'none') - .text(message); - // When modals are visible, display the alert in the modal layer above the - // grey background. - var visibleModal = $('.modal.in'); - if (visibleModal.size() > 0) { - // Check first if the model has an overlaying-alert. If not, append the - // overlaying-alert container. - if (visibleModal.find('.overlaying-alert').size() == 0) { - var $overlayingAlert = - $('
'); - visibleModal.append($overlayingAlert); - } - visibleModal.find('.overlaying-alert').prepend(alertBox); - } else { - $('#alert-messages').prepend(alertBox); - } - alertBox.fadeIn({ - complete: function() { - setTimeout(function() { - alertBox.slideUp(400, function() { - // On completion, remove the alert element from the DOM. - alertBox.remove(); - }); - }, 3000); - } - }); -} - - -/** - * Alerts a small success message in a overlaying alert box. - * @param {string} message Small message to display. - */ -function alertSuccess(message) { - alertMessage_(message, 'alert alert-success'); -} - - -/** - * Alerts a small error message in a overlaying alert box. - * @param {string} message Small message to display. - */ -function alertError(message) { - alertMessage_(message, 'alert alert-danger'); -} - - -/** - * Returns the active user (i.e. currentUser or lastUser). - * @return {!firebase.User} - */ -function activeUser() { - var type = $("input[name=toggle-user-selection]:checked").val(); - if (type == 'lastUser') { - return lastUser; - } else { - return auth.currentUser; - } -} - - -/** - * Refreshes the current user data in the UI, displaying a user info box if - * a user is signed in, or removing it. - */ -function refreshUserData() { - if (activeUser()) { - var user = activeUser(); - $('.profile').show(); - $('body').addClass('user-info-displayed'); - $('div.profile-email,span.profile-email').text(user.email || 'No Email'); - $('div.profile-phone,span.profile-phone') - .text(user.phoneNumber || 'No Phone'); - $('div.profile-uid,span.profile-uid').text(user.uid); - $('div.profile-name,span.profile-name').text(user.displayName || 'No Name'); - $('input.profile-name').val(user.displayName); - $('input.photo-url').val(user.photoURL); - if (user.photoURL != null) { - var photoURL = user.photoURL; - // Append size to the photo URL for Google hosted images to avoid requesting - // the image with its original resolution (using more bandwidth than needed) - // when it is going to be presented in smaller size. - if ((photoURL.indexOf('googleusercontent.com') != -1) || - (photoURL.indexOf('ggpht.com') != -1)) { - photoURL = photoURL + '?sz=' + $('img.profile-image').height(); - } - $('img.profile-image').attr('src', photoURL).show(); - } else { - $('img.profile-image').hide(); - } - $('.profile-email-verified').toggle(user.emailVerified); - $('.profile-email-not-verified').toggle(!user.emailVerified); - $('.profile-anonymous').toggle(user.isAnonymous); - // Display/Hide providers icons. - $('.profile-providers').empty(); - if (user['providerData'] && user['providerData'].length) { - var providersCount = user['providerData'].length; - for (var i = 0; i < providersCount; i++) { - addProviderIcon(user['providerData'][i]['providerId']); - } - } - // Show enrolled second factors if available for the active user. - showMultiFactorStatus(user); - // Change color. - if (user == auth.currentUser) { - $('#user-info').removeClass('last-user'); - $('#user-info').addClass('current-user'); - } else { - $('#user-info').removeClass('current-user'); - $('#user-info').addClass('last-user'); - } - } else { - $('.profile').slideUp(); - $('body').removeClass('user-info-displayed'); - $('input.profile-data').val(''); - } -} - - -/** - * Sets last signed in user and updates UI. - * @param {?firebase.User} user The last signed in user. - */ -function setLastUser(user) { - lastUser = user; - if (user) { - // Displays the toggle. - $('#toggle-user').show(); - $('#toggle-user-placeholder').hide(); - } else { - $('#toggle-user').hide(); - $('#toggle-user-placeholder').show(); - } -} - - -/** - * Add a provider icon to the profile info. - * @param {string} providerId The providerId of the provider. - */ -function addProviderIcon(providerId) { - var pElt = $('').addClass('fa ' + providersIcons[providerId]) - .attr('title', providerId) - .data({ - 'toggle': 'tooltip', - 'placement': 'bottom' - }); - $('.profile-providers').append(pElt); - pElt.tooltip(); -} - - -/** - * Updates the active user's multi-factor enrollment status. - * @param {!firebase.User} activeUser The corresponding user. - */ -function showMultiFactorStatus(activeUser) { - var enrolledFactors = - (activeUser.multiFactor && activeUser.multiFactor.enrolledFactors) || []; - var $listGroup = $('#user-info .dropdown-menu.enrolled-second-factors'); - // Hide the drop down menu initially. - $listGroup.empty().parent().hide(); - if (enrolledFactors.length) { - // If enrolled factors are available, show the drop down menu. - $listGroup.parent().show(); - // Populate the enrolled factors. - showMultiFactors( - $listGroup, - enrolledFactors, - // On row click, do nothing. This is needed to prevent the drop down - // menu from closing. - function(e) { - e.preventDefault(); - e.stopPropagation(); - }, - // On delete click unenroll the selected factor. - function(e) { - e.preventDefault(); - // Get the corresponding second factor index. - var index = parseInt($(this).attr('data-index'), 10); - // Get the second factor info. - var info = enrolledFactors[index]; - // Get the display name. If not available, use uid. - var label = info && (info.displayName || info.uid); - if (label) { - $('#enrolled-factors-drop-down').removeClass('open'); - activeUser.multiFactor - .unenroll(info) - .then(function() { - refreshUserData(); - alertSuccess('Multi-factor successfully unenrolled.'); - }, - onAuthError); - } - }); - } -} - - -/** - * Updates the UI when the user is successfully authenticated. - * @param {!firebase.User} user User authenticated. - */ -function onAuthSuccess(user) { - console.log(user); - alertSuccess('User authenticated, id: ' + user.uid); - refreshUserData(); -} - - -/** - * Displays an error message when the authentication failed. - * @param {!firebase.auth.Error} error Error message to display. - */ -function onAuthError(error) { - logAtLevel_(error, 'error'); - if (error.code == 'auth/multi-factor-auth-required') { - // Handle second factor sign-in. - handleMultiFactorSignIn(error.resolver); - } else { - alertError('Error: ' + error.code); - } -} - - -/** - * Changes the UI when the user has been signed out. - */ -function signOut() { - log('User successfully signed out.'); - alertSuccess('User successfully signed out.'); - refreshUserData(); -} - - -/** - * Saves the new language code provided in the language code input field. - */ -function onSetLanguageCode() { - var languageCode = $('#language-code').val() || null; - try { - auth.languageCode = languageCode; - alertSuccess('Language code changed to "' + languageCode + '".'); - } catch (error) { - alertError('Error: ' + error.code); - } -} - - -/** - * Switches Auth instance language to device language. - */ -function onUseDeviceLanguage() { - auth.useDeviceLanguage(); - $('#language-code').val(auth.languageCode); - alertSuccess('Using device language "' + auth.languageCode + '".'); -} - - -/** - * Changes the Auth state persistence to the specified one. - */ -function onSetPersistence() { - var type = $('#persistence-type').val(); - try { - auth.setPersistence(type).then(function() { - log('Persistence state change to "' + type + '".'); - alertSuccess('Persistence state change to "' + type + '".'); - }, function(error) { - alertError('Error: ' + error.code); - }); - } catch (error) { - alertError('Error: ' + error.code); - } -} - - -/** - * Signs up a new user with an email and a password. - */ -function onSignUp() { - var email = $('#signup-email').val(); - var password = $('#signup-password').val(); - auth.createUserWithEmailAndPassword(email, password) - .then(onAuthUserCredentialSuccess, onAuthError); -} - - -/** - * Signs in a user with an email and a password. - */ -function onSignInWithEmailAndPassword() { - var email = $('#signin-email').val(); - var password = $('#signin-password').val(); - auth.signInWithEmailAndPassword(email, password) - .then(onAuthUserCredentialSuccess, onAuthError); -} - - -/** - * Signs in a user with an email link. - */ -function onSignInWithEmailLink() { - var email = $('#sign-in-with-email-link-email').val(); - var link = $('#sign-in-with-email-link-link').val() || undefined; - if (auth.isSignInWithEmailLink(link)) { - auth.signInWithEmailLink(email, link).then(onAuthSuccess, onAuthError); - } else { - alertError('Sign in link is invalid'); - } -} - -/** - * Links a user with an email link. - */ -function onLinkWithEmailLink() { - var email = $('#link-with-email-link-email').val(); - var link = $('#link-with-email-link-link').val() || undefined; - var credential = firebase.auth.EmailAuthProvider - .credentialWithLink(email, link); - activeUser().linkWithCredential(credential) - .then(onAuthUserCredentialSuccess, onAuthError); -} - - -/** - * Re-authenticate a user with email link credential. - */ -function onReauthenticateWithEmailLink() { - var email = $('#link-with-email-link-email').val(); - var link = $('#link-with-email-link-link').val() || undefined; - var credential = firebase.auth.EmailAuthProvider - .credentialWithLink(email, link); - activeUser().reauthenticateWithCredential(credential) - .then(function(result) { - logAdditionalUserInfo(result); - refreshUserData(); - alertSuccess('User reauthenticated!'); - }, onAuthError); -} - - -/** - * Signs in with a custom token. - * @param {DOMEvent} event HTML DOM event returned by the listener. - */ -function onSignInWithCustomToken(event) { - // The token can be directly specified on the html element. - var token = $('#user-custom-token').val(); - - auth.signInWithCustomToken(token) - .then(onAuthUserCredentialSuccess, onAuthError); -} - - -/** - * Signs in anonymously. - */ -function onSignInAnonymously() { - auth.signInAnonymously() - .then(onAuthUserCredentialSuccess, onAuthError); -} - - -/** - * Signs in with a generic IdP credential. - */ -function onSignInWithGenericIdPCredential() { - var providerId = $('#signin-generic-idp-provider-id').val(); - var idToken = $('#signin-generic-idp-id-token').val() || undefined; - var rawNonce = $('#signin-generic-idp-raw-nonce').val() || undefined; - var accessToken = $('#signin-generic-idp-access-token').val() || undefined; - var provider = new firebase.auth.OAuthProvider(providerId); - auth.signInWithCredential( - provider.credential({ - idToken: idToken, - accessToken: accessToken, - rawNonce: rawNonce, - })).then(onAuthUserCredentialSuccess, onAuthError); -} - - -/** - * Initializes the ApplicationVerifier. - * @param {string} submitButtonId The ID of the DOM element of the button to - * which we attach the invisible reCAPTCHA. This is required even in visible - * mode. - */ -function makeApplicationVerifier(submitButtonId) { - var container = recaptchaSize === 'invisible' ? - submitButtonId : - 'recaptcha-container'; - applicationVerifier = new firebase.auth.RecaptchaVerifier(container, - {'size': recaptchaSize}); -} - - -/** - * Clears the ApplicationVerifier. - */ -function clearApplicationVerifier() { - if (applicationVerifier) { - applicationVerifier.clear(); - applicationVerifier = null; - } -} - - -/** - * Sends a phone number verification code for sign-in. - */ -function onSignInVerifyPhoneNumber() { - var phoneNumber = $('#signin-phone-number').val(); - var provider = new firebase.auth.PhoneAuthProvider(auth); - // Clear existing reCAPTCHA as an existing reCAPTCHA could be targeted for a - // link/re-auth operation. - clearApplicationVerifier(); - // Initialize a reCAPTCHA application verifier. - makeApplicationVerifier('signin-verify-phone-number'); - provider.verifyPhoneNumber(phoneNumber, applicationVerifier) - .then(function(verificationId) { - clearApplicationVerifier(); - $('#signin-phone-verification-id').val(verificationId); - alertSuccess('Phone verification sent!'); - }, function(error) { - clearApplicationVerifier(); - onAuthError(error); - }); -} - - -/** - * Confirms a phone number verification for sign-in. - */ -function onSignInConfirmPhoneVerification() { - var verificationId = $('#signin-phone-verification-id').val(); - var verificationCode = $('#signin-phone-verification-code').val(); - var credential = firebase.auth.PhoneAuthProvider.credential( - verificationId, verificationCode); - signInOrLinkCredential(credential); -} - - -/** - * Sends a phone number verification code for linking or reauth. - */ -function onLinkReauthVerifyPhoneNumber() { - var phoneNumber = $('#link-reauth-phone-number').val(); - var provider = new firebase.auth.PhoneAuthProvider(auth); - // Clear existing reCAPTCHA as an existing reCAPTCHA could be targeted for a - // sign-in operation. - clearApplicationVerifier(); - // Initialize a reCAPTCHA application verifier. - makeApplicationVerifier('link-reauth-verify-phone-number'); - provider.verifyPhoneNumber(phoneNumber, applicationVerifier) - .then(function(verificationId) { - clearApplicationVerifier(); - $('#link-reauth-phone-verification-id').val(verificationId); - alertSuccess('Phone verification sent!'); - }, function(error) { - clearApplicationVerifier(); - onAuthError(error); - }); -} - - -/** - * Updates the user's phone number. - */ -function onUpdateConfirmPhoneVerification() { - if (!activeUser()) { - alertError('You need to sign in before linking an account.'); - return; - } - var verificationId = $('#link-reauth-phone-verification-id').val(); - var verificationCode = $('#link-reauth-phone-verification-code').val(); - var credential = firebase.auth.PhoneAuthProvider.credential( - verificationId, verificationCode); - activeUser().updatePhoneNumber(credential).then(function() { - refreshUserData(); - alertSuccess('Phone number updated!'); - }, onAuthError); -} - - -/** - * Confirms a phone number verification for linking. - */ -function onLinkConfirmPhoneVerification() { - var verificationId = $('#link-reauth-phone-verification-id').val(); - var verificationCode = $('#link-reauth-phone-verification-code').val(); - var credential = firebase.auth.PhoneAuthProvider.credential( - verificationId, verificationCode); - signInOrLinkCredential(credential); -} - - -/** - * Confirms a phone number verification for reauthentication. - */ -function onReauthConfirmPhoneVerification() { - var verificationId = $('#link-reauth-phone-verification-id').val(); - var verificationCode = $('#link-reauth-phone-verification-code').val(); - var credential = firebase.auth.PhoneAuthProvider.credential( - verificationId, verificationCode); - activeUser().reauthenticateWithCredential(credential) - .then(function(result) { - logAdditionalUserInfo(result); - refreshUserData(); - alertSuccess('User reauthenticated!'); - }, onAuthError); -} - - -/** - * Sends a phone number verification code for enrolling second factor. - */ -function onStartEnrollWithPhoneMultiFactor() { - var phoneNumber = $('#enroll-mfa-phone-number').val(); - if (!phoneNumber || !activeUser()) { - return; - } - var provider = new firebase.auth.PhoneAuthProvider(auth); - // Clear existing reCAPTCHA as an existing reCAPTCHA could be targeted for a - // sign-in operation. - clearApplicationVerifier(); - // Initialize a reCAPTCHA application verifier. - makeApplicationVerifier('enroll-mfa-verify-phone-number'); - activeUser().multiFactor.getSession() - .then(function(multiFactorSession) { - var phoneInfoOptions = { - 'phoneNumber': phoneNumber, - 'session': multiFactorSession - }; - return provider.verifyPhoneNumber( - phoneInfoOptions, applicationVerifier); - }).then(function(verificationId) { - clearApplicationVerifier(); - $('#enroll-mfa-phone-verification-id').val(verificationId); - alertSuccess('Phone verification sent!'); - }, function(error) { - clearApplicationVerifier(); - onAuthError(error); - }); -} - - -/** - * Confirms a phone number verification for MFA enrollment. - */ -function onFinalizeEnrollWithPhoneMultiFactor() { - var verificationId = $('#enroll-mfa-phone-verification-id').val(); - var verificationCode = $('#enroll-mfa-phone-verification-code').val(); - if (!verificationId || !verificationCode || !activeUser()) { - return; - } - var credential = firebase.auth.PhoneAuthProvider.credential( - verificationId, verificationCode); - var multiFactorAssertion = - firebase.auth.PhoneMultiFactorGenerator.assertion(credential); - var displayName = $('#enroll-mfa-phone-display-name').val() || undefined; - - activeUser().multiFactor.enroll(multiFactorAssertion, displayName) - .then(function() { - refreshUserData(); - alertSuccess('Phone number enrolled!'); - }, onAuthError); -} - - -/** - * Signs in or links a provider's credential, based on current tab opened. - * @param {!firebase.auth.AuthCredential} credential The provider's credential. - */ -function signInOrLinkCredential(credential) { - if (currentTab == '#user-section') { - if (!activeUser()) { - alertError('You need to sign in before linking an account.'); - return; - } - activeUser().linkWithCredential(credential) - .then(function(result) { - logAdditionalUserInfo(result); - refreshUserData(); - alertSuccess('Provider linked!'); - }, onAuthError); - } else { - auth.signInWithCredential(credential) - .then(onAuthUserCredentialSuccess, onAuthError); - } -} - - -/** @return {!Object} The Action Code Settings object. */ -function getActionCodeSettings() { - var actionCodeSettings = {}; - var url = $('#continueUrl').val(); - var apn = $('#apn').val(); - var amv = $('#amv').val(); - var ibi = $('#ibi').val(); - var installApp = $("input[name=install-app]:checked").val() == 'Yes'; - var handleCodeInApp = $("input[name=handle-in-app]:checked").val() == 'Yes'; - if (url || apn || ibi) { - actionCodeSettings['url'] = url; - if (apn) { - actionCodeSettings['android'] = { - 'packageName': apn, - 'installApp': !!installApp, - 'minimumVersion': amv || undefined - }; - } - if (ibi) { - actionCodeSettings['iOS'] = { - 'bundleId': ibi - }; - } - actionCodeSettings['handleCodeInApp'] = handleCodeInApp; - } - return actionCodeSettings; -} - - -/** Reset action code settings form. */ -function onActionCodeSettingsReset() { - $('#continueUrl').val(''); - $('#apn').val(''); - $('#amv').val(''); - $('#ibi').val(''); -} - - -/** - * Changes the user's email. - */ -function onChangeEmail() { - var email = $('#changed-email').val(); - activeUser().updateEmail(email).then(function() { - refreshUserData(); - alertSuccess('Email changed!'); - }, onAuthError); -} - - -/** - * Changes the user's password. - */ -function onChangePassword() { - var password = $('#changed-password').val(); - activeUser().updatePassword(password).then(function() { - refreshUserData(); - alertSuccess('Password changed!'); - }, onAuthError); -} - - -/** - * Changes the user's password. - */ -function onUpdateProfile() { - var displayName = $('#display-name').val(); - var photoURL = $('#photo-url').val(); - activeUser().updateProfile({ - 'displayName': displayName, - 'photoURL': photoURL - }).then(function() { - refreshUserData(); - alertSuccess('Profile updated!'); - }, onAuthError); -} - - -/** - * Sends sign in with email link to the user. - */ -function onSendSignInLinkToEmail() { - var email = $('#sign-in-with-email-link-email').val(); - auth.sendSignInLinkToEmail(email, getActionCodeSettings()).then(function() { - alertSuccess('Email sent!'); - }, onAuthError); -} - -/** - * Sends sign in with email link to the user and pass in current url. - */ -function onSendSignInLinkToEmailCurrentUrl() { - var email = $('#sign-in-with-email-link-email').val(); - var actionCodeSettings = { - 'url': window.location.href, - 'handleCodeInApp': true - }; - - auth.sendSignInLinkToEmail(email, actionCodeSettings).then(function() { - if ('localStorage' in window && window['localStorage'] !== null) { - window.localStorage.setItem( - 'emailForSignIn', - // Save the email and the timestamp. - JSON.stringify({ - email: email, - timestamp: new Date().getTime() - })); - } - alertSuccess('Email sent!'); - }, onAuthError); -} - - -/** - * Sends email link to link the user. - */ -function onSendLinkEmailLink() { - var email = $('#link-with-email-link-email').val(); - auth.sendSignInLinkToEmail(email, getActionCodeSettings()).then(function() { - alertSuccess('Email sent!'); - }, onAuthError); -} - - -/** - * Sends password reset email to the user. - */ -function onSendPasswordResetEmail() { - var email = $('#password-reset-email').val(); - auth.sendPasswordResetEmail(email, getActionCodeSettings()).then(function() { - alertSuccess('Email sent!'); - }, onAuthError); -} - - -/** - * Verifies the password reset code entered by the user. - */ -function onVerifyPasswordResetCode() { - var code = $('#password-reset-code').val(); - auth.verifyPasswordResetCode(code).then(function() { - alertSuccess('Password reset code is valid!'); - }, onAuthError); -} - - -/** - * Confirms the password reset with the code and password supplied by the user. - */ -function onConfirmPasswordReset() { - var code = $('#password-reset-code').val(); - var password = $('#password-reset-password').val(); - auth.confirmPasswordReset(code, password).then(function() { - alertSuccess('Password has been changed!'); - }, onAuthError); -} - - -/** - * Gets the list of possible sign in methods for the given email address. - */ -function onFetchSignInMethodsForEmail() { - var email = $('#fetch-sign-in-methods-email').val(); - auth.fetchSignInMethodsForEmail(email).then(function(signInMethods) { - log('Sign in methods for ' + email + ' :'); - log(signInMethods); - if (signInMethods.length == 0) { - alertSuccess('Sign In Methods for ' + email + ': N/A'); - } else { - alertSuccess( - 'Sign In Methods for ' + email +': ' + signInMethods.join(', ')); - } - }, onAuthError); -} - - -/** - * Fetches and logs the user's providers data. - */ -function onGetProviderData() { - log('Providers data:'); - log(activeUser()['providerData']); -} - - -/** - * Links a signed in user with an email and password account. - */ -function onLinkWithEmailAndPassword() { - var email = $('#link-email').val(); - var password = $('#link-password').val(); - activeUser().linkWithCredential( - firebase.auth.EmailAuthProvider.credential(email, password)) - .then(onAuthUserCredentialSuccess, onAuthError); -} - - -/** - * Links with a generic IdP credential. - */ -function onLinkWithGenericIdPCredential() { - var providerId = $('#link-generic-idp-provider-id').val(); - var idToken = $('#link-generic-idp-id-token').val() || undefined; - var rawNonce = $('#link-generic-idp-raw-nonce').val() || undefined; - var accessToken = $('#link-generic-idp-access-token').val() || undefined; - var provider = new firebase.auth.OAuthProvider(providerId); - activeUser().linkWithCredential( - provider.credential({ - idToken: idToken, - accessToken: accessToken, - rawNonce: rawNonce, - })).then(onAuthUserCredentialSuccess, onAuthError); -} - - -/** - * Unlinks the specified provider. - */ -function onUnlinkProvider() { - var providerId = $('#unlinked-provider-id').val(); - activeUser().unlink(providerId).then(function(user) { - alertSuccess('Provider unlinked from user.'); - refreshUserData(); - }, onAuthError); -} - - -/** - * Sends email verification to the user. - */ -function onSendEmailVerification() { - activeUser().sendEmailVerification(getActionCodeSettings()).then(function() { - alertSuccess('Email verification sent!'); - }, onAuthError); -} - - -/** - * Confirms the email verification code given. - */ -function onApplyActionCode() { - var code = $('#email-verification-code').val(); - auth.applyActionCode(code).then(function() { - alertSuccess('Email successfully verified!'); - refreshUserData(); - }, onAuthError); -} - - -/** - * Gets or refreshes the ID token. - * @param {boolean} forceRefresh Whether to force the refresh of the token - * or not. - */ -function getIdToken(forceRefresh) { - if (activeUser() == null) { - alertError('No user logged in.'); - return; - } - if (activeUser().getIdToken) { - activeUser().getIdToken(forceRefresh).then(alertSuccess, function() { - log("No token"); - }); - } else { - activeUser().getToken(forceRefresh).then(alertSuccess, function() { - log("No token"); - }); - } -} - - -/** - * Gets or refreshes the ID token result. - * @param {boolean} forceRefresh Whether to force the refresh of the token - * or not - */ -function getIdTokenResult(forceRefresh) { - if (activeUser() == null) { - alertError('No user logged in.'); - return; - } - activeUser().getIdTokenResult(forceRefresh).then(function(idTokenResult) { - alertSuccess(JSON.stringify(idTokenResult)); - }, onAuthError); -} - - -/** - * Triggers the retrieval of the ID token result. - */ -function onGetIdTokenResult() { - getIdTokenResult(false); -} - - -/** - * Triggers the refresh of the ID token result. - */ -function onRefreshTokenResult() { - getIdTokenResult(true); -} - - -/** - * Triggers the retrieval of the ID token. - */ -function onGetIdToken() { - getIdToken(false); -} - - -/** - * Triggers the refresh of the ID token. - */ -function onRefreshToken() { - getIdToken(true); -} - - -/** - * Signs out the user. - */ -function onSignOut() { - setLastUser(auth.currentUser); - auth.signOut().then(signOut, onAuthError); -} - - -/** - * Handles multi-factor sign-in completion. - * @param {!firebase.auth.MultiFactorResolver} resolver The multi-factor error - * resolver. - */ -function handleMultiFactorSignIn(resolver) { - // Save multi-factor error resolver. - multiFactorErrorResolver = resolver; - // Populate 2nd factor options from resolver. - var $listGroup = $('#multiFactorModal div.enrolled-second-factors'); - // Populate the list of 2nd factors in the list group specified. - showMultiFactors( - $listGroup, - multiFactorErrorResolver.hints, - // On row click, select the corresponding second factor to complete - // sign-in with. - function(e) { - e.preventDefault(); - // Remove all other active entries. - $listGroup.find('a').removeClass('active'); - // Mark current entry as active. - $(this).addClass('active'); - // Select current factor. - onSelectMultiFactorHint(parseInt($(this).attr('data-index'), 10)); - }, - // Do not show delete option - null); - // Hide phone form (other second factor types could be supported). - $('#multi-factor-phone').addClass('hidden'); - // Show second factor recovery dialog. - $('#multiFactorModal').modal(); -} - - -/** - * Displays the list of multi-factors in the provided list group. - * @param {!jQuery} $listGroup The list group where the enrolled - * factors will be displayed. - * @param {!Array} multiFactorInfo The list of - * multi-factors to display. - * @param {?function(!jQuery.Event)} onClick The click handler when a second - * factor is clicked. - * @param {?function(!jQuery.Event)} onDelete The click handler when a second - * factor is delete. If not provided, no delete button is shown. - */ -function showMultiFactors($listGroup, multiFactorInfo, onClick, onDelete) { - // Append entry to list. - $listGroup.empty(); - $.each(multiFactorInfo, function(i) { - // Append entry to list. - var info = multiFactorInfo[i]; - var displayName = info.displayName || 'N/A'; - var $a = $('') - .addClass('list-group-item') - .addClass('list-group-item-action') - // Set index on entry. - .attr('data-index', i) - .appendTo($listGroup); - $a.append($('

').text(info.uid)); - $a.append($('').text(info.factorId)); - $a.append($('

').text(displayName)); - if (info.phoneNumber) { - $a.append($('').text(info.phoneNumber)); - } - // Check if a delete button is to be displayed. - if (onDelete) { - var $deleteBtn = $('' + - '' + - ''); - // Append delete button to row. - $a.append($deleteBtn); - // Add delete button click handler. - $a.find('button.delete-factor').click(onDelete); - } - // On entry click. - if (onClick) { - $a.click(onClick); - } - }); -} - - -/** - * Handles the user selection of second factor to complete sign-in with. - * @param {number} index The selected multi-factor hint index. - */ -function onSelectMultiFactorHint(index) { - // Hide all forms for handling each type of second factors. - // Currently only phone is supported. - $('#multi-factor-phone').addClass('hidden'); - if (!multiFactorErrorResolver || - typeof multiFactorErrorResolver.hints[index] === 'undefined') { - return; - } - - if (multiFactorErrorResolver.hints[index].factorId == 'phone') { - // Save selected second factor. - selectedMultiFactorHint = multiFactorErrorResolver.hints[index]; - // Show options for phone 2nd factor. - // Get reCAPTCHA ready. - clearApplicationVerifier(); - makeApplicationVerifier('send-2fa-phone-code'); - // Show sign-in with phone second factor menu. - $('#multi-factor-phone').removeClass('hidden'); - // Clear all input. - $('#multi-factor-sign-in-verification-id').val(''); - $('#multi-factor-sign-in-verification-code').val(''); - } else { - // 2nd factor not found or not supported by app. - alertError('Selected 2nd factor is not supported!'); - } -} - - -/** - * Start sign-in with the 2nd factor phone number. - * @param {!jQuery.Event} event The jQuery event object. - */ -function onStartSignInWithPhoneMultiFactor(event) { - event.preventDefault(); - // Make sure a second factor is selected. - if (!selectedMultiFactorHint || !multiFactorErrorResolver) { - return; - } - // Initialize a reCAPTCHA application verifier. - var provider = new firebase.auth.PhoneAuthProvider(auth); - var signInRequest = { - multiFactorHint: selectedMultiFactorHint, - session: multiFactorErrorResolver.session - }; - provider.verifyPhoneNumber(signInRequest, applicationVerifier) - .then(function(verificationId) { - clearApplicationVerifier(); - $('#multi-factor-sign-in-verification-id').val(verificationId); - alertSuccess('Phone verification sent!'); - }, function(error) { - clearApplicationVerifier(); - onAuthError(error); - }); -} - - -/** - * Completes sign-in with the 2nd factor phone assertion. - * @param {!jQuery.Event} event The jQuery event object. - */ -function onFinalizeSignInWithPhoneMultiFactor(event) { - event.preventDefault(); - var verificationId = $('#multi-factor-sign-in-verification-id').val(); - var code = $('#multi-factor-sign-in-verification-code').val(); - if (!code || !verificationId || !multiFactorErrorResolver) { - return; - } - var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, code); - var assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); - multiFactorErrorResolver.resolveSignIn(assertion) - .then(function(userCredential) { - onAuthUserCredentialSuccess(userCredential); - $('#multiFactorModal').modal('hide'); - }, onAuthError); -} - - -/** - * Adds a new row to insert an OAuth custom parameter key/value pair. - * @param {!jQuery.Event} event The jQuery event object. - */ -function onPopupRedirectAddCustomParam(event) { - // Form container. - var html = '

'; - // OAuth parameter key input. - html += ''; - // OAuth parameter value input. - html += ''; - // Button to remove current key/value pair. - html += ''; - html += ''; - // Create jQuery node. - var $node = $(html); - // Add button click event listener to remove item. - $node.find('button').on('click', function(e) { - // Remove button click event listener. - $(this).off('click'); - // Get row container and remove it. - $(this).closest('form.customParamItem').remove(); - e.preventDefault(); - }); - // Append constructed row to parameter list container. - $("#popup-redirect-custom-parameters").append($node); -} - - -/** - * Performs the corresponding popup/redirect action for a generic provider. - */ -function onPopupRedirectGenericProviderClick() { - var providerId = $('#popup-redirect-generic-providerid').val(); - var provider = new firebase.auth.OAuthProvider(providerId); - signInWithPopupRedirect(provider); -} - - -/** - * Performs the corresponding popup/redirect action for a SAML provider. - */ -function onPopupRedirectSamlProviderClick() { - var providerId = $('#popup-redirect-saml-providerid').val(); - var provider = new firebase.auth.SAMLAuthProvider(providerId); - signInWithPopupRedirect(provider); -} - - -/** - * Performs the corresponding popup/redirect action based on user's selection. - * @param {!jQuery.Event} event The jQuery event object. - */ -function onPopupRedirectProviderClick(event) { - var providerId = $(event.currentTarget).data('provider'); - var provider = null; - switch (providerId) { - case 'google.com': - provider = new firebase.auth.GoogleAuthProvider(); - break; - case 'facebook.com': - provider = new firebase.auth.FacebookAuthProvider(); - break; - case 'github.com': - provider = new firebase.auth.GithubAuthProvider(); - break; - case 'twitter.com': - provider = new firebase.auth.TwitterAuthProvider(); - break; - default: - return; - } - signInWithPopupRedirect(provider); -} - - -/** - * Performs a popup/redirect action based on a given provider and the user's - * selections. - * @param {!firebase.auth.AuthProvider} provider The provider with which to - * sign in. - */ -function signInWithPopupRedirect(provider) { - var action = $("input[name=popup-redirect-action]:checked").val(); - var type = $("input[name=popup-redirect-type]:checked").val(); - var method = null; - var inst = null; - if (action == 'link' || action == 'reauthenticate') { - if (!activeUser()) { - alertError('No user logged in.'); - return; - } - inst = activeUser(); - method = action + 'With'; - } else { - inst = auth; - method = 'signInWith'; - } - if (type == 'popup') { - method += 'Popup'; - } else { - method += 'Redirect'; - } - // Get custom OAuth parameters. - var customParameters = {}; - // For each entry. - $('form.customParamItem').each(function(index) { - // Get parameter key. - var key = $(this).find('input.customParamKey').val(); - // Get parameter value. - var value = $(this).find('input.customParamValue').val(); - // Save to list if valid. - if (key && value) { - customParameters[key] = value; - } - }); - console.log('customParameters: ', customParameters); - // For older jscore versions that do not support this. - if (provider.setCustomParameters) { - // Set custom parameters on current provider. - provider.setCustomParameters(customParameters); - } - - // Add scopes for providers who do have scopes available (i.e. not Twitter). - if (provider.addScope) { - // String.prototype.trim not available in IE8. - var scopes = $.trim($('#scopes').val()).split(/\s*,\s*/); - for (var i = 0; i < scopes.length; i++) { - provider.addScope(scopes[i]); - } - } - console.log('Provider:'); - console.log(provider); - if (type == 'popup') { - inst[method](provider).then(function(response) { - console.log('Popup response:'); - console.log(response); - alertSuccess(action + ' with ' + provider['providerId'] + ' successful!'); - logAdditionalUserInfo(response); - onAuthSuccess(activeUser()); - }, onAuthError); - } else { - try { - inst[method](provider).catch(onAuthError); - } catch (error) { - console.log('Error while calling ' + method); - console.error(error); - } - } -} - - -/** - * Displays user credential result. - * @param {!firebase.auth.UserCredential} result The UserCredential result - * object. - */ -function onAuthUserCredentialSuccess(result) { - onAuthSuccess(result.user); - logAdditionalUserInfo(result); -} - - -/** - * Displays redirect result. - */ -function onGetRedirectResult() { - auth.getRedirectResult().then(function(response) { - log('Redirect results:'); - if (response.credential) { - log('Credential:'); - log(response.credential); - } else { - log('No credential'); - } - if (response.user) { - log('User\'s id:'); - log(response.user.uid); - } else { - log('No user'); - } - logAdditionalUserInfo(response); - console.log(response); - }, onAuthError); -} - - -/** - * Logs additional user info returned by a sign-in event, if available. - * @param {!Object} response - */ -function logAdditionalUserInfo(response) { - if (response.additionalUserInfo) { - if (response.additionalUserInfo.username) { - log(response.additionalUserInfo['providerId'] + ' username: ' + - response.additionalUserInfo.username); - } - if (response.additionalUserInfo.profile) { - log(response.additionalUserInfo['providerId'] + ' profile information:'); - log(JSON.stringify(response.additionalUserInfo.profile, null, 2)); - } - if (typeof response.additionalUserInfo.isNewUser !== 'undefined') { - log(response.additionalUserInfo['providerId'] + ' isNewUser: ' + - response.additionalUserInfo.isNewUser); - } - if (response.credential) { - log('credential: ' + JSON.stringify(response.credential.toJSON())); - } - } -} - - - -/** - * Deletes the user account. - */ -function onDelete() { - activeUser()['delete']().then(function() { - log('User successfully deleted.'); - alertSuccess('User successfully deleted.'); - refreshUserData(); - }, onAuthError); -} - - -/** - * Gets a specific query parameter from the current URL. - * @param {string} name Name of the parameter. - * @return {string} The query parameter requested. - */ -function getParameterByName(name) { - var url = window.location.href; - name = name.replace(/[\[\]]/g, '\\$&'); - var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); - var results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, ' ')); -} - - -/** - * Detects if an action code is passed in the URL, and populates accordingly - * the input field for the confirm email verification process. - */ -function populateActionCodes() { - var emailForSignIn = null; - var signInTime = 0; - if ('localStorage' in window && window['localStorage'] !== null) { - try { - // Try to parse as JSON first using new storage format. - var emailForSignInData = - JSON.parse(window.localStorage.getItem('emailForSignIn')); - emailForSignIn = emailForSignInData['email'] || null; - signInTime = emailForSignInData['timestamp'] || 0; - } catch (e) { - // JSON parsing failed. This means the email is stored in the old string - // format. - emailForSignIn = window.localStorage.getItem('emailForSignIn'); - } - if (emailForSignIn) { - // Clear old codes. Old format codes should be cleared immediately. - if (new Date().getTime() - signInTime >= 1 * 24 * 3600 * 1000) { - // Remove email from storage. - window.localStorage.removeItem('emailForSignIn'); - } - } - } - var actionCode = getParameterByName('oobCode'); - if (actionCode != null) { - var mode = getParameterByName('mode'); - if (mode == 'verifyEmail') { - $('#email-verification-code').val(actionCode); - } else if (mode == 'resetPassword') { - $('#password-reset-code').val(actionCode); - } else if (mode == 'signIn') { - if (emailForSignIn) { - $('#sign-in-with-email-link-email').val(emailForSignIn); - $('#sign-in-with-email-link-link').val(window.location.href); - onSignInWithEmailLink(); - // Remove email from storage as the code is only usable once. - window.localStorage.removeItem('emailForSignIn'); - } - } else { - $('#email-verification-code').val(actionCode); - $('#password-reset-code').val(actionCode); - } - } -} - - -/** - * Provides basic Database checks for authenticated and unauthenticated access. - * The Database node being tested has the following rule: - * "users": { - * "$user_id": { - * ".read": "$user_id === auth.uid", - * ".write": "$user_id === auth.uid" - * } - * } - * This applies when Real-time database service is available. - */ -function checkDatabaseAuthAccess() { - var randomString = Math.floor(Math.random() * 10000000).toString(); - var dbRef; - var dbPath; - var errMessage; - // Run this check only when Database module is available. - if (typeof firebase !== 'undefined' && - typeof firebase.database !== 'undefined') { - if (lastUser && !firebase.auth().currentUser) { - dbPath = 'users/' + lastUser.uid; - // After sign out, confirm read/write access to users/$user_id blocked. - dbRef = firebase.database().ref(dbPath); - dbRef.set({ - 'test': randomString - }).then(function() { - alertError( - 'Error: Unauthenticated write to Database node ' + dbPath + - ' unexpectedly succeeded!'); - }).catch(function(error) { - errMessage = error.message.toLowerCase(); - // Permission denied error should be thrown. - if (errMessage.indexOf('permission_denied') == -1) { - alertError('Error: ' + error.code); - return; - } - dbRef.once('value') - .then(function() { - alertError('Error: Unauthenticated read to Database node ' + - dbPath + ' unexpectedly succeeded!'); - }).catch(function(error) { - errMessage = error.message.toLowerCase(); - // Permission denied error should be thrown. - if (errMessage.indexOf('permission_denied') == -1) { - alertError('Error: ' + error.code); - return; - } - log('Unauthenticated read/write to Database node ' + dbPath + - ' failed as expected!'); - }); - }); - } else if (firebase.auth().currentUser) { - dbPath = 'users/' + firebase.auth().currentUser.uid; - // Confirm read/write access to users/$user_id allowed. - dbRef = firebase.database().ref(dbPath); - dbRef.set({ - 'test': randomString - }).then(function() { - return dbRef.once('value'); - }).then(function(snapshot) { - if (snapshot.val().test === randomString) { - // read/write successful. - log('Authenticated read/write to Database node ' + dbPath + - ' succeeded!'); - } else { - throw new Error('Authenticated read/write to Database node ' + - dbPath + ' failed!'); - } - // Clean up: clear that node's content. - return dbRef.remove(); - }).catch(function(error) { - alertError('Error: ' + error.code); - }); - } - } -} - - -/** Runs all web worker tests if web workers are supported. */ -function onRunWebWorkTests() { - if (!webWorker) { - alertError('Error: Web workers are not supported in the current browser!'); - return; - } - var onError = function(error) { - alertError('Error code: ' + error.code + ' message: ' + error.message); - }; - auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()) - .then(function(result) { - runWebWorkerTests(result.credential.idToken); - }, onError); -} - - -/** Runs service worker tests if supported. */ -function onRunServiceWorkTests() { - $.ajax('/checkIfAuthenticated').then(function(data, textStatus, jqXHR) { - alertSuccess('User authenticated: ' + data.uid); - }, function(jqXHR, textStatus, errorThrown) { - alertError(jqXHR.status + ': ' + JSON.stringify(jqXHR.responseJSON)); - }); -} - - -/** Copy current user of auth to tempAuth. */ -function onCopyActiveUser() { - tempAuth.updateCurrentUser(activeUser()).then(function() { - alertSuccess('Copied active user to temp Auth'); - }, function(error) { - alertError('Error: ' + error.code); - }); -} - - -/** Copy last user to auth. */ -function onCopyLastUser() { - // If last user is null, NULL_USER error will be thrown. - auth.updateCurrentUser(lastUser).then(function() { - alertSuccess('Copied last user to Auth'); - }, function(error) { - alertError('Error: ' + error.code); - }); -} - - -/** Applies selected auth settings change. */ -function onApplyAuthSettingsChange() { - try { - auth.settings.appVerificationDisabledForTesting = - $("input[name=enable-app-verification]:checked").val() == 'No'; - alertSuccess('Auth settings changed'); - } catch (error) { - alertError('Error: ' + error.code); - } -} - - -/** - * Initiates the application by setting event listeners on the various buttons. - */ -function initApp(){ - log('Initializing app...'); - app = firebase.initializeApp(config); - auth = app.auth(); - if (window.emulatorUrl) { - auth.useEmulator(emulatorUrl); - } - - tempApp = firebase.initializeApp({ - 'apiKey': config['apiKey'], - 'authDomain': config['authDomain'] - }, auth['app']['name'] + '-temp'); - tempAuth = tempApp.auth(); - if (window.emulatorUrl) { - tempAuth.useEmulator(emulatorUrl); - } - - // Listen to reCAPTCHA config togglers. - initRecaptchaToggle(function(size) { - clearApplicationVerifier(); - recaptchaSize = size; - }); - - // The action code for email verification or password reset - // can be passed in the url address as a parameter, and for convenience - // this preloads the input field. - populateActionCodes(); - - // Allows to login the user if previously logged in. - if (auth.onIdTokenChanged) { - auth.onIdTokenChanged(function(user) { - refreshUserData(); - if (user) { - user.getIdTokenResult(false).then( - function(idTokenResult) { - log(JSON.stringify(idTokenResult)); - }, - function() { - log('No token.'); - } - ); - } else { - log('No user logged in.'); - } - }); - } - - if (auth.onAuthStateChanged) { - auth.onAuthStateChanged(function(user) { - if (user) { - log('user state change detected: ' + user.uid); - } else { - log('user state change detected: no user'); - } - // Check Database Auth access. - checkDatabaseAuthAccess(); - }); - } - - if (tempAuth.onAuthStateChanged) { - tempAuth.onAuthStateChanged(function(user) { - if (user) { - log('user state change on temp Auth detect: ' + JSON.stringify(user)); - alertSuccess('user state change on temp Auth detect: ' + user.uid); - } - }); - } - - // We check for redirect result to refresh user's data. - auth.getRedirectResult().then(function(response) { - refreshUserData(); - logAdditionalUserInfo(response); - }, onAuthError); - - // Bootstrap tooltips. - $('[data-toggle="tooltip"]').tooltip(); - - // Auto submit the choose library type form. - $('#library-form').on('change', 'input.library-option', function() { - $('#library-form').submit(); - }); - - // To clear the logs in the page. - $('.clear-logs').click(clearLogs); - - // Disables JS forms. - $('form.no-submit').on('submit', function() { - return false; - }); - - // Keeps track of the current tab opened. - $('#tab-menu a').click(function(event) { - currentTab = $(event.currentTarget).attr("href"); - }); - - // Toggles user. - $('input[name=toggle-user-selection]').change(refreshUserData); - - // Actions listeners. - $('#sign-up-with-email-and-password').click(onSignUp); - $('#sign-in-with-email-and-password').click(onSignInWithEmailAndPassword); - $('.sign-in-with-custom-token').click(onSignInWithCustomToken); - $('#sign-in-anonymously').click(onSignInAnonymously); - $('#sign-in-with-generic-idp-credential') - .click(onSignInWithGenericIdPCredential); - $('#signin-verify-phone-number').click(onSignInVerifyPhoneNumber); - $('#signin-confirm-phone-verification') - .click(onSignInConfirmPhoneVerification); - // On enter click in verification code, complete phone sign-in. This prevents - // reCAPTCHA from being re-rendered (default behavior on enter). - $('#signin-phone-verification-code').keypress(function(e) { - if (e.which == 13) { - onSignInConfirmPhoneVerification(); - e.preventDefault(); - } - }); - $('#sign-in-with-email-link').click(onSignInWithEmailLink); - $('#link-with-email-link').click(onLinkWithEmailLink); - $('#reauth-with-email-link').click(onReauthenticateWithEmailLink); - - $('#change-email').click(onChangeEmail); - $('#change-password').click(onChangePassword); - $('#update-profile').click(onUpdateProfile); - - $('#send-sign-in-link-to-email').click(onSendSignInLinkToEmail); - $('#send-sign-in-link-to-email-current-url') - .click(onSendSignInLinkToEmailCurrentUrl); - $('#send-link-email-link').click(onSendLinkEmailLink); - - $('#send-password-reset-email').click(onSendPasswordResetEmail); - $('#verify-password-reset-code').click(onVerifyPasswordResetCode); - $('#confirm-password-reset').click(onConfirmPasswordReset); - - $('#get-provider-data').click(onGetProviderData); - $('#link-with-email-and-password').click(onLinkWithEmailAndPassword); - $('#link-with-generic-idp-credential').click(onLinkWithGenericIdPCredential); - $('#unlink-provider').click(onUnlinkProvider); - $('#link-reauth-verify-phone-number').click(onLinkReauthVerifyPhoneNumber); - $('#update-confirm-phone-verification') - .click(onUpdateConfirmPhoneVerification); - $('#link-confirm-phone-verification').click(onLinkConfirmPhoneVerification); - $('#reauth-confirm-phone-verification') - .click(onReauthConfirmPhoneVerification); - // On enter click in verification code, complete phone sign-in. This prevents - // reCAPTCHA from being re-rendered (default behavior on enter). - $('#link-reauth-phone-verification-code').keypress(function(e) { - if (e.which == 13) { - // User first option option as default. - onUpdateConfirmPhoneVerification(); - e.preventDefault(); - } - }); - - $('#send-email-verification').click(onSendEmailVerification); - $('#confirm-email-verification').click(onApplyActionCode); - $('#get-token-result').click(onGetIdTokenResult); - $('#refresh-token-result').click(onRefreshTokenResult); - $('#get-token').click(onGetIdToken); - $('#refresh-token').click(onRefreshToken); - $('#get-token-worker').click(onGetCurrentUserDataFromWebWorker); - $('#sign-out').click(onSignOut); - - $('.popup-redirect-provider').click(onPopupRedirectProviderClick); - $('#popup-redirect-generic').click(onPopupRedirectGenericProviderClick); - $('#popup-redirect-get-redirect-result').click(onGetRedirectResult); - $('#popup-redirect-add-custom-parameter') - .click(onPopupRedirectAddCustomParam); - $('#popup-redirect-saml').click(onPopupRedirectSamlProviderClick); - - $('#action-code-settings-reset').click(onActionCodeSettingsReset); - - $('#delete').click(onDelete); - - $('#set-persistence').click(onSetPersistence); - - $('#set-language-code').click(onSetLanguageCode); - $('#use-device-language').click(onUseDeviceLanguage); - - $('#fetch-sign-in-methods-for-email').click(onFetchSignInMethodsForEmail); - - $('#run-web-worker-tests').click(onRunWebWorkTests); - $('#run-service-worker-tests').click(onRunServiceWorkTests); - $('#copy-active-user').click(onCopyActiveUser); - $('#copy-last-user').click(onCopyLastUser); - - $('#apply-auth-settings-change').click(onApplyAuthSettingsChange); - - // Multi-factor operations. - // Starts multi-factor sign-in with selected phone number. - $('#send-2fa-phone-code').click(onStartSignInWithPhoneMultiFactor); - // Completes multi-factor sign-in with supplied SMS code. - $('#sign-in-with-phone-multi-factor').click( - onFinalizeSignInWithPhoneMultiFactor); - // Starts multi-factor enrollment with phone number. - $('#enroll-mfa-verify-phone-number').click(onStartEnrollWithPhoneMultiFactor); - // Completes multi-factor enrollment with supplied SMS code. - $('#enroll-mfa-confirm-phone-verification') - .click(onFinalizeEnrollWithPhoneMultiFactor); -} - -$(initApp); diff --git a/packages/auth/demo/public/service-worker.js b/packages/auth/demo/public/service-worker.js deleted file mode 100644 index 3518e50888c..00000000000 --- a/packages/auth/demo/public/service-worker.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Service worker for Firebase Auth test app application. The - * service worker caches all content and only serves cached content in offline - * mode. - */ - -importScripts('/dist/firebase-app.js'); -importScripts('/dist/firebase-auth.js'); -importScripts('config.js'); - -// Initialize the Firebase app in the web worker. -firebase.initializeApp(config); - -var CACHE_NAME = 'cache-v1'; -var urlsToCache = [ - '/', - '/manifest.json', - '/config.js', - '/script.js', - '/common.js', - '/style.css', - '/dist/firebase-app.js', - '/dist/firebase-auth.js', - '/dist/firebase-database.js' -]; - -/** - * Returns a promise that resolves with an ID token if available. - * @return {!Promise} The promise that resolves with an ID token if - * available. Otherwise, the promise resolves with null. - */ -var getIdToken = function() { - return new Promise(function(resolve, reject) { - firebase.auth().onAuthStateChanged(function(user) { - if (user) { - user.getIdToken().then(function(idToken) { - resolve(idToken); - }, function(error) { - resolve(null); - }); - } else { - resolve(null); - } - }); - }).catch(function(error) { - console.log(error); - }); -}; - - -/** - * @param {string} url The URL whose origin is to be returned. - * @return {string} The origin corresponding to given URL. - */ -var getOriginFromUrl = function(url) { - // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript - var pathArray = url.split('/'); - var protocol = pathArray[0]; - var host = pathArray[2]; - return protocol + '//' + host; -}; - - -self.addEventListener('install', function(event) { - // Perform install steps. - event.waitUntil(caches.open(CACHE_NAME).then(function(cache) { - // Add all URLs of resources we want to cache. - return cache.addAll(urlsToCache) - .catch(function(error) { - // Suppress error as some of the files may not be available for the - // current page. - }); - })); -}); - -// As this is a test app, let's only return cached data when offline. -self.addEventListener('fetch', function (event) { - var fetchEvent = event; - var requestProcessor = function(idToken) { - var req = event.request; - // For same origin https requests, append idToken to header. - if (self.location.origin == getOriginFromUrl(event.request.url) && - (self.location.protocol == 'https:' || - self.location.hostname == 'localhost') && - idToken) { - // Clone headers as request headers are immutable. - var headers = new Headers(); - for (var entry of req.headers.entries()) { - headers.append(entry[0], entry[1]); - } - // Add ID token to header. We can't add to Authentication header as it - // will break HTTP basic authentication. - headers.append('x-id-token', idToken); - try { - req = new Request(req.url, { - method: req.method, - headers: headers, - mode: 'same-origin', - credentials: req.credentials, - cache: req.cache, - redirect: req.redirect, - referrer: req.referrer, - body: req.body, - bodyUsed: req.bodyUsed, - context: req.context - }); - } catch (e) { - // This will fail for CORS requests. We just continue with the - // fetch caching logic below and do not pass the ID token. - } - } - return fetch(req).then(function(response) { - // Check if we received a valid response. - // If not, just funnel the error response. - if (!response || response.status !== 200 || response.type !== 'basic') { - return response; - } - // If response is valid, clone it and save it to the cache. - var responseToCache = response.clone(); - // Save response to cache. - caches.open(CACHE_NAME).then(function(cache) { - cache.put(fetchEvent.request, responseToCache); - }); - // After caching, return response. - return response; - }) - .catch(function(error) { - // For fetch errors, attempt to retrieve the resource from cache. - return caches.match(fetchEvent.request.clone()); - }) - .catch(function(error) { - // If error getting resource from cache, do nothing. - console.log(error); - }); - }; - // Try to fetch the resource first after checking for the ID token. - event.respondWith(getIdToken().then(requestProcessor, requestProcessor)); -}); - -self.addEventListener('activate', function (event) { - if (window.emulatorUrl) { - firebase.auth().useEmulator(emulatorUrl); - } - // Update this list with all caches that need to remain cached. - var cacheWhitelist = ['cache-v1']; - event.waitUntil(caches.keys().then(function(cacheNames) { - return Promise.all(cacheNames.map(function(cacheName) { - // Check if cache is not whitelisted above. - if (cacheWhitelist.indexOf(cacheName) === -1) { - // If not whitelisted, delete it. - return caches.delete(cacheName); - } - })); - })); -}); diff --git a/packages/auth/demo/public/web-worker.js b/packages/auth/demo/public/web-worker.js deleted file mode 100644 index 13c4d171496..00000000000 --- a/packages/auth/demo/public/web-worker.js +++ /dev/null @@ -1,193 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Web worker for Firebase Auth test app application. The - * web worker tries to run operations on the Auth instance for testing purposes. - */ - -importScripts('/dist/firebase-app.js'); -importScripts('/dist/firebase-auth.js'); -importScripts('config.js'); - - -// Initialize the Firebase app in the web worker. -firebase.initializeApp(config); - -/** - * Returns a promise that resolves with an ID token if available. - * @return {!Promise} The promise that resolves with an ID token if - * available. Otherwise, the promise resolves with null. - */ -var getIdToken = function() { - return new Promise(function(resolve, reject) { - firebase.auth().onAuthStateChanged(function(user) { - if (user) { - user.getIdToken().then(function(idToken) { - resolve(idToken); - }, function(error) { - resolve(null); - }); - } else { - resolve(null); - } - }); - }).catch(function(error) { - console.log(error); - }); -}; - -/** - * Runs various Firebase Auth tests in a web worker environment and confirms the - * expected behavior. This is useful for manual testing in different browsers. - * @param {string} googleIdToken The Google ID token to sign in with. - * @return {!Promise} A promise that resolves when all tests run - * successfully. - */ -var runWorkerTests = function(googleIdToken) { - var inMemoryPersistence = firebase.auth.Auth.Persistence.NONE; - var expectedDisplayName = 'Test User'; - var oauthCredential = firebase.auth.GoogleAuthProvider.credential( - googleIdToken); - var provider = new firebase.auth.GoogleAuthProvider(); - var OPERATION_NOT_SUPPORTED_CODE = - 'auth/operation-not-supported-in-this-environment'; - var email = 'user' + Math.floor(Math.random() * 10000000000).toString() + - '@example.com'; - var pass = 'password'; - return firebase.auth().setPersistence(inMemoryPersistence) - .then(function() { - firebase.auth().useDeviceLanguage(); - return firebase.auth().signInAnonymously(); - }) - .then(function(result) { - if (!result.user.uid) { - throw new Error('signInAnonymously unexpectedly failed!'); - } - return result.user.updateProfile({displayName: expectedDisplayName}); - }) - .then(function() { - if (firebase.auth().currentUser.displayName != expectedDisplayName) { - throw new Error('Profile update failed!'); - } - return firebase.auth().currentUser.delete(); - }) - .then(function() { - if (firebase.auth().currentUser) { - throw new Error('currentUser.delete unexpectedly failed!'); - } - return firebase.auth().createUserWithEmailAndPassword(email, pass); - }) - .then(function(result) { - if (result.user.email != email) { - throw new Error( - 'createUserWithEmailAndPassword unexpectedly failed!'); - } - return firebase.auth().fetchProvidersForEmail(email); - }).then(function(providers) { - if (providers.length == 0 || providers[0] != 'password') { - throw new Error('fetchProvidersForEmail failed!'); - } - return firebase.auth().signInWithEmailAndPassword(email, pass); - }) - .then(function(result) { - if (result.user.email != email) { - throw new Error('signInWithEmailAndPassword unexpectedly failed!'); - } - return result.user.delete(); - }) - .then(function() { - return firebase.auth().signInWithPopup(provider) - .catch(function(error) { - if (error.code != OPERATION_NOT_SUPPORTED_CODE) { - throw error; - } - }); - }) - .then(function() { - return firebase.auth().signInWithRedirect(provider) - .catch(function(error) { - if (error.code != OPERATION_NOT_SUPPORTED_CODE) { - throw error; - } - }); - }) - .then(function() { - return Promise.resolve().then(function() { - return new firebase.auth.RecaptchaVerifier('id'); - }).then(function() { - throw new Error( - 'RecaptchaVerifer instantiation succeeded unexpectedly!'); - }).catch(function(error) { - if (error.code != OPERATION_NOT_SUPPORTED_CODE) { - throw error; - } - }); - }) - .then(function() { - return firebase.auth().signInWithCredential( - oauthCredential); - }) - .then(function(result) { - if (!result.user || - !result.user.uid || - !result.credential || - !result.additionalUserInfo) { - throw new Error( - 'signInWithCredential unexpectedly failed!'); - } - return firebase.auth().signOut(); - }) - .then(function() { - if (firebase.auth().currentUser) { - throw new Error('signOut unexpectedly failed!'); - } - }); -}; - -/** - * Handles the incoming message from the main script. - * @param {!Object} e The message event received. - */ -self.onmessage = function(e) { - if (e.data && e.data.type) { - var result = {type: e.data.type}; - switch (e.data.type) { - case 'GET_USER_INFO': - getIdToken().then(function(idToken) { - result.idToken = idToken; - result.uid = firebase.auth().currentUser && - firebase.auth().currentUser.uid; - self.postMessage(result); - }); - break; - case 'RUN_TESTS': - runWorkerTests(e.data.googleIdToken).then(function() { - result.status = 'success'; - self.postMessage(result); - }).catch(function(error) { - result.status = 'failure'; - // DataCloneError when postMessaging in IE11 and 10. - result.error = error.code ? error : error.message; - self.postMessage(result); - }); - break; - default: - self.postMessage({}); - } - } -}; diff --git a/packages-exp/auth-exp/demo/rollup.config.js b/packages/auth/demo/rollup.config.js similarity index 100% rename from packages-exp/auth-exp/demo/rollup.config.js rename to packages/auth/demo/rollup.config.js diff --git a/packages-exp/auth-exp/demo/src/config.d.ts b/packages/auth/demo/src/config.d.ts similarity index 100% rename from packages-exp/auth-exp/demo/src/config.d.ts rename to packages/auth/demo/src/config.d.ts diff --git a/packages-exp/auth-exp/demo/src/index.js b/packages/auth/demo/src/index.js similarity index 99% rename from packages-exp/auth-exp/demo/src/index.js rename to packages/auth/demo/src/index.js index 9ae38f876a9..44ec1b9206a 100644 --- a/packages-exp/auth-exp/demo/src/index.js +++ b/packages/auth/demo/src/index.js @@ -22,7 +22,7 @@ * package. */ -import { initializeApp } from '@firebase/app-exp'; +import { initializeApp } from '@firebase/app'; import { applyActionCode, browserLocalPersistence, @@ -68,7 +68,7 @@ import { reauthenticateWithRedirect, getRedirectResult, browserPopupRedirectResolver -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { config } from './config'; import { diff --git a/packages-exp/auth-exp/demo/src/logging.js b/packages/auth/demo/src/logging.js similarity index 100% rename from packages-exp/auth-exp/demo/src/logging.js rename to packages/auth/demo/src/logging.js diff --git a/packages-exp/auth-exp/demo/src/sample-config.js b/packages/auth/demo/src/sample-config.js similarity index 100% rename from packages-exp/auth-exp/demo/src/sample-config.js rename to packages/auth/demo/src/sample-config.js diff --git a/packages-exp/auth-exp/demo/src/worker/service-worker.ts b/packages/auth/demo/src/worker/service-worker.ts similarity index 98% rename from packages-exp/auth-exp/demo/src/worker/service-worker.ts rename to packages/auth/demo/src/worker/service-worker.ts index d6316465619..924fd20f560 100644 --- a/packages-exp/auth-exp/demo/src/worker/service-worker.ts +++ b/packages/auth/demo/src/worker/service-worker.ts @@ -20,8 +20,8 @@ * service worker caches all content and only serves cached content in offline * mode. */ -import { initializeApp } from '@firebase/app-exp'; -import { getAuth, User } from '@firebase/auth-exp'; +import { initializeApp } from '@firebase/app'; +import { getAuth, User } from '@firebase/auth'; import { config } from '../config'; diff --git a/packages-exp/auth-exp/demo/src/worker/tsconfig.json b/packages/auth/demo/src/worker/tsconfig.json similarity index 100% rename from packages-exp/auth-exp/demo/src/worker/tsconfig.json rename to packages/auth/demo/src/worker/tsconfig.json diff --git a/packages-exp/auth-exp/demo/src/worker/web-worker.ts b/packages/auth/demo/src/worker/web-worker.ts similarity index 98% rename from packages-exp/auth-exp/demo/src/worker/web-worker.ts rename to packages/auth/demo/src/worker/web-worker.ts index 39a089e7d56..79800907f7b 100644 --- a/packages-exp/auth-exp/demo/src/worker/web-worker.ts +++ b/packages/auth/demo/src/worker/web-worker.ts @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { initializeApp } from '@firebase/app-exp'; +import { initializeApp } from '@firebase/app'; import { createUserWithEmailAndPassword, fetchSignInMethodsForEmail, @@ -25,7 +25,7 @@ import { signInWithEmailAndPassword, updateProfile, User -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { config } from '../config'; diff --git a/packages-exp/auth-exp/demo/tsconfig.json b/packages/auth/demo/tsconfig.json similarity index 100% rename from packages-exp/auth-exp/demo/tsconfig.json rename to packages/auth/demo/tsconfig.json diff --git a/packages/auth/externs/externs.js b/packages/auth/externs/externs.js deleted file mode 100644 index 502df137d3a..00000000000 --- a/packages/auth/externs/externs.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase Auth-specific externs. - */ - - -/** - * A verifier that asserts that the user calling an API is a real user. - * @interface - */ -firebase.auth.ApplicationVerifier = function() {}; - - -/** - * The type of the ApplicationVerifier assertion, e.g. "recaptcha". - * @type {string} - */ -firebase.auth.ApplicationVerifier.prototype.type; - - -/** - * Returns a promise for the assertion to verify the app identity, e.g. the - * g-recaptcha-response in reCAPTCHA. - * @return {!firebase.Promise} - */ -firebase.auth.ApplicationVerifier.prototype.verify = function() {}; diff --git a/packages/auth/externs/gapi.iframes.js b/packages/auth/externs/gapi.iframes.js deleted file mode 100644 index 92b033a090c..00000000000 --- a/packages/auth/externs/gapi.iframes.js +++ /dev/null @@ -1,503 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Provide gapi.iframes public api. - * - * @externs - */ - - -var gapi = {}; - -/** - * Namespace associated with gapi iframes API. - * @const - */ -gapi.iframes = {}; - -/** - * Type for options bag for create and open functions. - * Please use gapix.iframes.Options to construct the options. - * (See javascript/abc/libs/gapi/iframes/options.js) - * @typedef {Object} - **/ -gapi.iframes.OptionsBag; - -/** - * Type of iframes filter function. - * @typedef {function(gapi.iframes.Iframe):boolean} - **/ -gapi.iframes.IframesFilter; - -/** - * Message handlers type. The iframe the message came from is passed in as - * 'this'. The handler can return any value or a Promise for an async response. - * @typedef {function(this:gapi.iframes.Iframe, *, - * !gapi.iframes.Iframe): (*|Thenable)} - **/ -gapi.iframes.MessageHandler; - -/** - * Sent message callback function type. - * @typedef {function(Array<*>)} - **/ -gapi.iframes.SendCallback; - -/** - * Style function which processes an open request parameter set. - * It can create the new iframe container and style it, - * and update the open request parameters accordingly. - * It can add message handlers to support style specific behavior. - * @typedef {function(gapi.iframes.OptionsBag)} - */ -gapi.iframes.StyleHandler; - -/** - * Message filter handler type. - * @typedef {function(this:gapi.iframes.Iframe, *): - * (boolean|IThenable)} - **/ -gapi.iframes.RpcFilter; - -/** - * Create a new iframe, pass abc context. - * @param {string} url the url for the opened iframe. - * @param {Element} whereToPut the location to put the new iframe. - * @param {gapi.iframes.OptionsBag=} opt_options extra options for the iframe. - * @return {Element} the new iframe dom element. - */ -gapi.iframes.create = function(url, whereToPut, opt_options) {}; - -/** - * Class to handle the iframes context. - * This contains info about the current iframe - parent, pub/sub etc. - * In most cases there will be one object for this (selfContext), - * but for controller iframe, separate object will be created for - * each controlled iframe. - * @param {gapi.iframes.OptionsBag=} opt_options Context override options. - * @constructor - */ -gapi.iframes.Context = function(opt_options) {}; - -/** - * Get the default context for current frame. - * @return {gapi.iframes.Context} The current context. - */ -gapi.iframes.getContext = function() {}; - -/** - * Implement an iframes filter to check same origin connection. - * @param {gapi.iframes.Iframe} iframe - The iframe to check for same - * origin as current context. - * @return {boolean} true if the iframe has same domain has the context. - */ -gapi.iframes.SAME_ORIGIN_IFRAMES_FILTER = function(iframe) {}; - -/** - * Implement a filter that accept any iframe. - * This should be used only if the message handler sanitize the data, - * and the code that use it must go through security review. - * @param {gapi.iframes.Iframe} iframe The iframe to check. - * @return {boolean} always true. - */ -gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER = function(iframe) {}; - -/** - * Create an iframes filter that allow iframes from a list of origins. - * @param {Array} origins List of allowed origins. - * @return {gapi.iframes.IframesFilter} New iframes filter that allow, - * iframes only from the provided origins. - */ -gapi.iframes.makeWhiteListIframesFilter = function(origins) {}; - -/** - * Check if the context was disposed. - * @return {boolean} True if the context was disposed. - */ -gapi.iframes.Context.prototype.isDisposed = function() {}; - -/** - * @return {string} Iframe current page frame name. - */ -gapi.iframes.Context.prototype.getFrameName = function() {}; - -/** - * Get the context window object. - * @return {Window} The window object. - */ -gapi.iframes.Context.prototype.getWindow = function() {}; - -/** - * Get context global parameters. - * @param {string} key Parameter name. - * @return {*} Parameter value. - */ -gapi.iframes.Context.prototype.getGlobalParam = function(key) {}; - -/** - * Set context global parameters. - * @param {string} key Parameter name. - * @param {*} value Parameter value. - */ -gapi.iframes.Context.prototype.setGlobalParam = function(key, value) {}; - -/** - * Register a new style. - * @param {string} style The new style name. - * @param {gapi.iframes.StyleHandler} func The style handler. - */ -gapi.iframes.registerStyle = function(style, func) {}; - -/** - * Register a new style to handle options before relaying request. - * @param {string} style The new style name. - * @param {gapi.iframes.StyleHandler} func The style handler. - */ -gapi.iframes.registerBeforeOpenStyle = function(style, func) {}; - -/** - * Get style hanlder. - * @param {string} style The new style name. - * @return {gapi.iframes.StyleHandler} The style handler. - */ -gapi.iframes.getStyle = function(style) {}; - -/** - * Get a style hanlder for open options before relaying request. - * @param {string} style The new style name. - * @return {gapi.iframes.StyleHandler} The style handler. - */ -gapi.iframes.getBeforeOpenStyle = function(style) {}; - -/** - * Open a new child iframe and attach rpc to it. - * @param {!gapi.iframes.OptionsBag} options Open parameters. - * @return {!gapi.iframes.Iframe} The new Iframe object. - */ -gapi.iframes.Context.prototype.openChild = function(options) {}; - -/** - * Open a new iframe, support relay open to parent or other iframe. - * @param {!gapi.iframes.OptionsBag} options Open parameters. - * @param {function(gapi.iframes.Iframe)=} opt_callback Callback to be called. - * with the created iframe. - * @return {!IThenable} The created iframe. - */ -gapi.iframes.Context.prototype.open = function(options, opt_callback) {}; - -/** - * Get the context parent Iframe if available. - * (Available if current iframe has an id). - * @return {gapi.iframes.Iframe} Parent iframe. - */ -gapi.iframes.Context.prototype.getParentIframe = function() {}; - -/** - * An Iframe object to represent an iframe that can be communicated with. - * Use send to send a message to the iframe, and register to set a handler - * for a message from the iframe. - * @param {gapi.iframes.Context} context New iframe context. - * @param {string} rpcAddr rpc routing to the iframe. - * @param {string} frameName The frame-name the rpc messages are identified by. - * @param {gapi.iframes.OptionsBag} options Iframe options. - * @constructor - */ -gapi.iframes.Iframe = function(context, rpcAddr, frameName, options) {}; - -/** - * Check if the iframe was disposed. - * @return {boolean} True if the iframe was disposed. - */ -gapi.iframes.Iframe.prototype.isDisposed = function() {}; - -/** - * Get the Iframe context. - * @return {gapi.iframes.Context} Iframe context. - */ -gapi.iframes.Iframe.prototype.getContext = function() {}; - -/** - * Get the Iframe name. - * @return {string} Iframe frame-name. - */ -gapi.iframes.Iframe.prototype.getFrameName = function() {}; - -/** - * @return {string} Iframe id. - */ -gapi.iframes.Iframe.prototype.getId = function() {}; - -/** - * Get Iframe parameters. - * @param {string} key Parameter name. - * @return {*} Parameter value. - */ -gapi.iframes.Iframe.prototype.getParam = function(key) {}; - -/** - * Get Iframe parameters. - * @param {string} key Parameter name. - * @param {*} value Parameter value. - */ -gapi.iframes.Iframe.prototype.setParam = function(key, value) {}; - -/** - * Register a message handler. - * The handler should have two parameters: the Iframe object and message data. - * @param {string} message The message to register for. - * @param {gapi.iframes.MessageHandler} func Message handler. - * @param {gapi.iframes.IframesFilter=} opt_filter Optional iframe filter, - * Default is same origin filter, which is not used if overridden. - */ -gapi.iframes.Iframe.prototype.register = function(message, func, opt_filter) {}; - -/** - * Un-register a message handler. - * @param {string} message Message to unregister from. - * @param {gapi.iframes.MessageHandler=} opt_func Optional message handler, - * if specified only that handler is unregistered, - * otherwise all handlers for the message are unregistered. - */ -gapi.iframes.Iframe.prototype.unregister = function(message, opt_func) {}; - -/** - * Send a message to the Iframe. - * If there is no handler for the message, it will be queued, - * and the callback will be called only when an handler is registered. - * @param {string} message Message name. - * @param {*=} opt_data The data to send to the iframe. - * @param {gapi.iframes.SendCallback=} opt_callback Callback function to call - * with return values of handler for the message (list). - * @param {gapi.iframes.IframesFilter=} opt_filter Optional iframe filter, - * Default is same origin filter, which is not used if overridden. - * @return {!IThenable} Array of return values of all handlers. - */ -gapi.iframes.Iframe.prototype.send = - function(message, opt_data, opt_callback, opt_filter) {}; - -/** -`* Send a ping to the iframe whcih echo back the optional data. - * Useful to check if the iframe is responsive/correct. - * @param {!gapi.iframes.SendCallback} callback Callback function to call - * with return values (array of first element echo of opt_data). - * @param {*=} opt_data The data to send to the iframe. - * @return {!IThenable} Array of return values of all handlers. - */ -gapi.iframes.Iframe.prototype.ping = function(callback, opt_data) {}; - -/** - * Add iframes api registry. - * @param {string} apiName The api name. - * @param {Object} registry - * Map of handlers. - * @param {gapi.iframes.IframesFilter=} opt_filter Optional iframe filter, - * Default is same origin filter, which is not used if overridden. - */ -gapi.iframes.registerIframesApi = function(apiName, registry, opt_filter) {}; - -/** - * Utility function to build api by adding handler one by one. - * Should be used on initialization time only - * (is not applied on already opened iframes). - * @param {string} apiName The api name. - * @param {string} message The message name to register an handler for. - * @param {gapi.iframes.MessageHandler} handler The handler to register. - */ -gapi.iframes.registerIframesApiHandler = function(apiName, message, handler) {}; - -/** - * Apply an iframes api on the iframe. - * @param {string} api Name of the api. - */ -gapi.iframes.Iframe.prototype.applyIframesApi = function(api) {}; - -/** - * Get the dom node for the iframe. - * Return null if the iframe is not a direct child iframe. - * @return {?Element} the iframe dom node. - */ -gapi.iframes.Iframe.prototype.getIframeEl = function() {}; - -/** - * Get the iframe container dom node. - * The site element can be override by the style when - * the container of the iframe is more then simple parent. - * The site element is used as reference when positioning - * other iframes relative the an iframe. - * @return {?Element} The iframe container dom node. - */ -gapi.iframes.Iframe.prototype.getSiteEl = function() {}; - -/** - * Set the iframe container dom node. - * Can be used by style code to indicate a more complex dom - * to contain the iframe. - * @param {!Element} el The iframe container dom node. - */ -gapi.iframes.Iframe.prototype.setSiteEl = function(el) {}; - -/** - * Get the Window object of the remote iframe. - * It is only supported for same origin iframes, otherwise return null. - * @return {?Window} The window object for the iframe or null. - */ -gapi.iframes.Iframe.prototype.getWindow = function() {}; - -/** - * Get the iframe url origin. - * @return {string} Iframe url origin. - */ -gapi.iframes.Iframe.prototype.getOrigin = function() {}; - -/** - * Send a request to close the iframe. - * @param {*=} opt_params Optional parameters. - * @param {gapi.iframes.SendCallback=} opt_callback - * Optional callback to indicate close was done or canceled. - * @return {!IThenable} Array of return values of all handlers. - */ -gapi.iframes.Iframe.prototype.close = function(opt_params, opt_callback) {}; - -/** - * Send a request to change the iframe style. - * @param {*} styleData Restyle parameters. - * @param {gapi.iframes.SendCallback=} opt_callback - * Optional callback to indicate restyle was done or canceled. - * @return {!IThenable} Array of return values of all handlers. - */ -gapi.iframes.Iframe.prototype.restyle = function(styleData, opt_callback) {}; - -/** - * Register a handler on relayed open iframe to get restyle status. - * Called in the context of the opener, to handle style changes events. - * @param {gapi.iframes.MessageHandler} handler Message handler. - * @param {gapi.iframes.IframesFilter=} opt_filter Optional iframe filter, - * default is same origin filter, which is not used if overridden. - */ -gapi.iframes.Iframe.prototype.registerWasRestyled = - function(handler, opt_filter) {}; - -/** - * Register a callback to be notified on iframe closed. - * Called in the context of the opener, to handle close event. - * @param {gapi.iframes.MessageHandler} handler Message handler. - * @param {gapi.iframes.IframesFilter=} opt_filter Optional iframe filter, - * Default is same origin filter, which is not used if overrided. - */ -gapi.iframes.Iframe.prototype.registerWasClosed = function( - handler, opt_filter) {}; - -/** - * Close current iframe (send request to parent). - * @param {*=} opt_params Optional parameters. - * @param {function(this:gapi.iframes.Iframe, boolean)=} opt_callback - * Optional callback to indicate close was done or canceled. - * @return {!IThenable} True if close request was issued, - * false if close was denied. - */ -gapi.iframes.Context.prototype.closeSelf = - function(opt_params, opt_callback) {}; - -/** - * Restyle current iframe (send request to parent). - * @param {*} styleData Restyle parameters. - * @param {function(this:gapi.iframes.Iframe, boolean)=} opt_callback - * Optional callback to indicate restyle was done or canceled. - * @return {!IThenable} True if restyle request was issued, - * false if restyle was denied. - */ -gapi.iframes.Context.prototype.restyleSelf = - function(styleData, opt_callback) {}; - -/** - * Style helper function to indicate current iframe is ready. - * It send ready both to parent and opener, and register methods on - * the opener. - * Adds calculated height of current page if height not provided. - * @param {Object=} opt_params Ready message data. - * @param {Object=} opt_methods - * Map of message handler to register on the opener. - * @param {gapi.iframes.SendCallback=} opt_callback Ready message callback. - * @param {gapi.iframes.IframesFilter=} opt_filter Optional iframe filter, - * used for sending ready and register provided methods to opener. - */ -gapi.iframes.Context.prototype.ready = - function(opt_params, opt_methods, opt_callback, opt_filter) {}; - -/** - * Provide a filter for close request on current iframe. - * @param {gapi.iframes.RpcFilter} filter Filter function. - * @return {undefined} - */ -gapi.iframes.Context.prototype.setCloseSelfFilter = function(filter) {}; - -/** - * Provide a filter for restyle request on current iframe. - * @param {gapi.iframes.RpcFilter} filter Filter function. - * @return {undefined} - */ -gapi.iframes.Context.prototype.setRestyleSelfFilter = function(filter) {}; - - -/** - * Connect between two iframes, provide the subject each side - * is subscribed to. - * @param {gapi.iframes.OptionsBag} iframe1Data - one side of connection. - * @param {gapi.iframes.OptionsBag=} opt_iframe2Data - other side of connection. - * Supported options: - * iframe: the iframe to connect to. - * role: the role to send to that iframe (optional). - * data: the data to send to that iframe (optional). - * isReady: indicate that we do not need for ready from the other side. - */ -gapi.iframes.Context.prototype.connectIframes = - function(iframe1Data, opt_iframe2Data) {}; - -/** - * Configure how to handle new connection by role. - * Provide a filter, list of apis, and callback to be notified on connection. - * @param {string|gapi.iframes.OptionsBag} optionsOrRole Connection options - * object (see gapix.iframes.OnConnectOptions}, or connect role. - * Options are preferred, and if provided the next optional params will - * be ignored. - * @param {function(!gapi.iframes.Iframe, Object<*>=)=} opt_handler Callback - * for the new connection. - * @param {Array=} opt_apis List of iframes apis to apply to - * connected iframes. - * @param {gapi.iframes.IframesFilter=} opt_filter Optional iframe filter, - * Default is same origin filter, which is not used if overridden. - */ -gapi.iframes.Context.prototype.addOnConnectHandler = - function(optionsOrRole, opt_handler, opt_apis, opt_filter) {}; - -/** - * Remove all on connect handlers for a role. - * @param {string} role Connection role. - */ -gapi.iframes.Context.prototype.removeOnConnectHandler = function(role) {}; - -/** - * Helper function to set handler for the opener connection. - * @param {function(gapi.iframes.Iframe)} handler Callback for new connection. - * @param {Array=} opt_apis List of iframes apis to apply to - * connected iframes. - * @param {gapi.iframes.IframesFilter=} opt_filter Optional iframe filter, - * Default is same origin filter, which is not used if overridden. - */ -gapi.iframes.Context.prototype.addOnOpenerHandler = - function(handler, opt_apis, opt_filter) {}; diff --git a/packages/auth/externs/grecaptcha.js b/packages/auth/externs/grecaptcha.js deleted file mode 100644 index 49aa3c7cca8..00000000000 --- a/packages/auth/externs/grecaptcha.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Public externs for the recaptcha javascript API. - * @externs - */ - - -/** - * The namespace for reCaptcha V2. - * - * https://developers.google.com/recaptcha/docs/display#js_api - */ -var grecaptcha = {}; - - -/** - * Creates a new instance of the recaptcha client. - * - * @param {(!Element|string)} elementOrId Element or element id for the - * placeholder to render the recaptcha client. - * @param {!Object} params Parameters for the recaptcha client. - * @return {number} The client id. - */ -grecaptcha.render = function(elementOrId, params) {}; - - -/** - * Resets a client with the given id. If an id is not provided, resets the - * default client. - * - * @param {number=} opt_id The id of the recaptcha client. - * @param {?Object=} opt_params Parameters for the recaptcha client. - */ -grecaptcha.reset = function(opt_id, opt_params) {}; - - -/** - * Gets the response for the client with the given id. If an id is not - * provided, gets the response for the default client. - * - * @param {number=} opt_id The id of the recaptcha client. - * @return {string} - */ -grecaptcha.getResponse = function(opt_id) {}; - - -/** - * Programmatically triggers the invisible reCAPTCHA. - * - * @param {number=} opt_id The id of the recaptcha client. - */ -grecaptcha.execute = function(opt_id) {}; diff --git a/packages/auth/gulpfile.js b/packages/auth/gulpfile.js deleted file mode 100644 index aa9e76b8de3..00000000000 --- a/packages/auth/gulpfile.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const gulp = require('gulp'); -const closureCompiler = require('google-closure-compiler').gulp(); -const del = require('del'); -const express = require('express'); -const path = require('path'); -const sourcemaps = require('gulp-sourcemaps'); -const pkg = require('./package.json'); - -// The optimization level for the JS compiler. -// Valid levels: WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS. -// TODO: Add ability to pass this in as a flag. -const OPTIMIZATION_LEVEL = 'ADVANCED_OPTIMIZATIONS'; - -// For minified builds, wrap the output so we avoid leaking global variables. -const CJS_WRAPPER_PREFIX = - `(function() {var firebase = require('@firebase/app').default;`; -const ESM_WRAPPER_PREFIX = `import firebase from '@firebase/app';(function() {`; -const WRAPPER_SUFFIX = - `}).apply(typeof global !== 'undefined' ? ` + - `global : typeof self !== 'undefined' ? ` + - `self : typeof window !== 'undefined' ? window : {});`; - -const closureLibRoot = path.dirname( - require.resolve('google-closure-library/package.json') -); - -/** - * Builds the core Firebase-auth JS. - * @param {string} filename name of the generated file - * @param {string} prefix prefix to the compiled code - * @param {string} suffix suffix to the compiled code - */ -function createBuildTask(filename, prefix, suffix) { - return () => - gulp - .src([ - `${closureLibRoot}/closure/goog/**/*.js`, - `${closureLibRoot}/third_party/closure/goog/**/*.js`, - 'src/**/*.js' - ], { base: '.' }) - .pipe(sourcemaps.init()) - .pipe( - closureCompiler({ - js_output_file: filename, - output_wrapper: `${prefix}%output%${suffix}`, - entry_point: 'fireauth.exports', - compilation_level: OPTIMIZATION_LEVEL, - externs: [ - 'externs/externs.js', - 'externs/grecaptcha.js', - 'externs/gapi.iframes.js', - path.resolve( - __dirname, - '../firebase/externs/firebase-app-externs.js' - ), - path.resolve( - __dirname, - '../firebase/externs/firebase-error-externs.js' - ), - path.resolve( - __dirname, - '../firebase/externs/firebase-app-internal-externs.js' - ) - ], - language_out: 'ES5', - only_closure_dependencies: true, - // Insert current package.json version into registerVersion call. - define: `AUTH_NPM_PACKAGE_VERSION=${pkg.version}` - }) - ) - .pipe(sourcemaps.write('.')) - .pipe(gulp.dest('dist')); -} - -// commonjs build -const cjsBuild = createBuildTask('auth.js', CJS_WRAPPER_PREFIX, WRAPPER_SUFFIX); -gulp.task('cjs', cjsBuild); - -// esm build -const esmBuild = createBuildTask('auth.esm.js', ESM_WRAPPER_PREFIX, WRAPPER_SUFFIX); -gulp.task('esm', esmBuild); - -// build without wrapper -const unwrappedBuild = createBuildTask('unwrapped.js', '', ''); -gulp.task('build-firebase-auth-js', unwrappedBuild); - -// Deletes intermediate files. -gulp.task('clean', done => del(['dist/*', 'dist'], done)); - -// Creates a webserver that serves all files from the root of the package. -gulp.task('serve', () => { - const app = express(); - - app.use( - '/node_modules', - express.static(path.resolve(__dirname, '../../node_modules')) - ); - app.use(express.static(__dirname)); - - app.listen(4001); -}); - -gulp.task('default', gulp.parallel('cjs', 'esm')); diff --git a/packages-exp/auth-exp/index.cordova.ts b/packages/auth/index.cordova.ts similarity index 97% rename from packages-exp/auth-exp/index.cordova.ts rename to packages/auth/index.cordova.ts index 15e5d784725..e5ecf1ed428 100644 --- a/packages-exp/auth-exp/index.cordova.ts +++ b/packages/auth/index.cordova.ts @@ -22,7 +22,7 @@ * just use index.ts */ -import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; import { Auth } from './src/model/public_types'; import { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db'; @@ -48,7 +48,7 @@ export { import { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect/popup_redirect'; export function getAuth(app: FirebaseApp = getApp()): Auth { - const provider = _getProvider(app, 'auth-exp'); + const provider = _getProvider(app, 'auth'); if (provider.isInitialized()) { return provider.getImmediate(); diff --git a/packages/auth/index.d.ts b/packages/auth/index.d.ts deleted file mode 100644 index 316db8d5303..00000000000 --- a/packages/auth/index.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import "@firebase/auth-types"; diff --git a/packages-exp/auth-exp/index.doc.ts b/packages/auth/index.doc.ts similarity index 93% rename from packages-exp/auth-exp/index.doc.ts rename to packages/auth/index.doc.ts index a89d97aa7db..8b1699f6d20 100644 --- a/packages-exp/auth-exp/index.doc.ts +++ b/packages/auth/index.doc.ts @@ -1,3 +1,9 @@ +/** + * Firebase Authentication + * + * @packageDocumentation + */ + /** * @license * Copyright 2021 Google LLC diff --git a/packages-exp/auth-exp/index.node.ts b/packages/auth/index.node.ts similarity index 100% rename from packages-exp/auth-exp/index.node.ts rename to packages/auth/index.node.ts diff --git a/packages-exp/auth-exp/index.rn.ts b/packages/auth/index.rn.ts similarity index 97% rename from packages-exp/auth-exp/index.rn.ts rename to packages/auth/index.rn.ts index 4ee51e02e8f..209b1a53b00 100644 --- a/packages-exp/auth-exp/index.rn.ts +++ b/packages/auth/index.rn.ts @@ -24,7 +24,7 @@ import { AsyncStorage } from 'react-native'; -import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; import { Auth, Persistence } from './src/model/public_types'; import { initializeAuth } from './src'; @@ -45,7 +45,7 @@ export const reactNativeLocalPersistence: Persistence = getReactNativePersistence(AsyncStorage); export function getAuth(app: FirebaseApp = getApp()): Auth { - const provider = _getProvider(app, 'auth-exp'); + const provider = _getProvider(app, 'auth'); if (provider.isInitialized()) { return provider.getImmediate(); diff --git a/packages-exp/auth-exp/index.shared.ts b/packages/auth/index.shared.ts similarity index 100% rename from packages-exp/auth-exp/index.shared.ts rename to packages/auth/index.shared.ts diff --git a/packages-exp/auth-exp/index.ts b/packages/auth/index.ts similarity index 100% rename from packages-exp/auth-exp/index.ts rename to packages/auth/index.ts diff --git a/packages-exp/auth-exp/index.webworker.ts b/packages/auth/index.webworker.ts similarity index 99% rename from packages-exp/auth-exp/index.webworker.ts rename to packages/auth/index.webworker.ts index 4b237572fa2..d7903f1a1f1 100644 --- a/packages-exp/auth-exp/index.webworker.ts +++ b/packages/auth/index.webworker.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; +import { _getProvider, FirebaseApp, getApp } from '@firebase/app'; import { Auth } from './src/model/public_types'; import { AuthImpl } from './src/core/auth/auth_impl'; diff --git a/packages-exp/auth-exp/internal/index.ts b/packages/auth/internal/index.ts similarity index 98% rename from packages-exp/auth-exp/internal/index.ts rename to packages/auth/internal/index.ts index d8da272c895..aeaf0833e27 100644 --- a/packages-exp/auth-exp/internal/index.ts +++ b/packages/auth/internal/index.ts @@ -19,7 +19,7 @@ import { _castAuth } from '../src/core/auth/auth_impl'; import { Auth } from '../src/model/public_types'; /** - * This interface is intended only for use by @firebase/auth-compat-exp, do not use directly + * This interface is intended only for use by @firebase/auth-compat, do not use directly */ export * from '../index'; diff --git a/packages-exp/auth-exp/internal/package.json b/packages/auth/internal/package.json similarity index 88% rename from packages-exp/auth-exp/internal/package.json rename to packages/auth/internal/package.json index 4c6482ee076..8dd06ec9378 100644 --- a/packages-exp/auth-exp/internal/package.json +++ b/packages/auth/internal/package.json @@ -1,5 +1,5 @@ { - "name": "@firebase/auth-exp/internal", + "name": "@firebase/auth/internal", "description": "An internal version of the Auth SDK for use in the compatibility layer", "main": "../dist/node/internal.js", "module": "../dist/esm2017/internal.js", diff --git a/packages-exp/auth-exp/karma.conf.js b/packages/auth/karma.conf.js similarity index 100% rename from packages-exp/auth-exp/karma.conf.js rename to packages/auth/karma.conf.js diff --git a/packages/auth/package.json b/packages/auth/package.json index 97c5b0c6dac..79337cffc5b 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,41 +1,86 @@ { "name": "@firebase/auth", "version": "0.16.8", - "main": "dist/auth.js", - "browser": "dist/auth.esm.js", - "module": "dist/auth.esm.js", - "description": "Javascript library for Firebase Auth SDK", + "description": "The Firebase Authenticaton component of the Firebase JS SDK.", "author": "Firebase (https://firebase.google.com/)", + "main": "dist/node/index.js", + "react-native": "dist/rn/index.js", + "browser": "dist/esm2017/index.js", + "cordova": "dist/cordova/index.esm5.js", + "module": "dist/esm2017/index.js", + "webworker": "dist/index.webworker.esm5.js", "files": [ "dist", - "index.d.ts" + "cordova/package.json", + "internal/package.json", + "react-native/package.json" ], "scripts": { - "build": "gulp", + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/auth --include-dependencies build", - "demo": "./buildtools/run_demo.sh", - "generate-test-files": "./buildtools/generate_test_files.sh", - "serve": "yarn build && yarn generate-test-files && gulp serve", - "test": "yarn generate-test-files && ./buildtools/run_tests.sh", - "test:ci": "node ../../scripts/run_tests_in_ci.js" + "build:release": "yarn build && yarn typings:public", + "build:scripts": "tsc -moduleResolution node --module commonjs scripts/*.ts && ls scripts/*.js | xargs -I % sh -c 'terser % -o %'", + "dev": "rollup -c -w", + "test": "run-p lint test:all", + "test:all": "run-p test:browser:unit test:node:unit test:integration", + "test:integration": "firebase emulators:exec --project emulatedproject --only auth \"run-s test:browser:integration:local test:node:integration:local test:webdriver\"", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:integration:local": "run-s test:node:integration:local test:browser:integration:local test:webdriver", + "test:browser": "karma start --single-run --local", + "test:browser:unit": "karma start --single-run --unit", + "test:browser:integration": "karma start --single-run --integration", + "test:browser:integration:local": "karma start --single-run --integration --local", + "test:browser:debug": "karma start --auto-watch", + "test:browser:unit:debug": "karma start --auto-watch --unit", + "test:cordova": "karma start --single-run --cordova", + "test:cordova:debug": "karma start --auto-watch --cordova", + "test:node": "run-s test:node:unit test:node:integration:local", + "test:node:unit": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts", + "test:node:integration": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts --integration", + "test:node:integration:local": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts --integration --local", + "test:webdriver": "rollup -c test/integration/webdriver/static/rollup.config.js && ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts --webdriver", + "api-report": "api-extractor run --local --verbose && ts-node-script ../../repo-scripts/prune-dts/prune-dts.ts --input dist/auth-public.d.ts --output dist/auth-public.d.ts", + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/auth-public.d.ts" + }, + "peerDependencies": { + "@firebase/app": "0.x" }, - "license": "Apache-2.0", "dependencies": { - "@firebase/auth-types": "0.10.3" + "@firebase/component": "0.5.6", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.3.0", + "node-fetch": "2.6.1", + "selenium-webdriver": "4.0.0-beta.1", + "tslib": "^2.1.0" }, + "license": "Apache-2.0", "devDependencies": { - "firebase-tools": "9.14.0", - "google-closure-compiler": "20200112.0.0", - "google-closure-library": "20200830.0.0", - "gulp": "4.0.2", - "gulp-sourcemaps": "3.0.0" + "@firebase/app": "0.6.30", + "@rollup/plugin-json": "4.1.0", + "rollup": "2.52.2", + "rollup-plugin-sourcemaps": "0.6.3", + "rollup-plugin-typescript2": "0.30.0", + "@rollup/plugin-strip": "2.0.1", + "typescript": "4.2.2" }, "repository": { "directory": "packages/auth", "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, - "peerDependencies": { - "@firebase/app": "0.x" - } -} + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/auth.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "esm5": "dist/esm5/index.js" +} \ No newline at end of file diff --git a/packages/auth/protractor.conf.js b/packages/auth/protractor.conf.js deleted file mode 100644 index dbbf6469b85..00000000000 --- a/packages/auth/protractor.conf.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Stores the configuration of Protractor. It is loaded by protractor to run - * tests. - * - * Usage: - * - * Run locally: - * $ npm test - * It will start a local Selenium Webdriver server as well as the HTTP server - * that serves test files. - * - * Run locally using SauceLabs: - * Go to your SauceLab account, under "My Account", and copy paste the - * access key. Now export the following variables: - * $ export SAUCE_USERNAME= - * $ export SAUCE_ACCESS_KEY= - * Then, start SauceConnect: - * $ ./buildtools/sauce_connect.sh - * Take note of the "Tunnel Identifier" value logged in the terminal. - * Run the tests: - * $ npm test -- --saucelabs --tunnelIdentifier= - * This will start the HTTP Server locally, and connect through SauceConnect - * to SauceLabs remote browsers instances. - * - * Travis will run `npm test -- --saucelabs`. - */ - -// Common configuration. -config = { - // Using jasmine to wrap Closure JSUnit tests. - framework: 'jasmine', - // The jasmine specs to run. - specs: ['protractor_spec.js'], - // Jasmine options. Increase the timeout to 5min instead of the default 30s. - jasmineNodeOpts: { - // Default time to wait in ms before a test fails. - defaultTimeoutInterval: 5 * 60 * 1000 - } -}; - -// Read arguments to the protractor command. -// The first 3 arguments are something similar to: -// [ '.../bin/node', -// '.../node_modules/.bin/protractor', -// 'protractor.conf.js' ] -var arguments = process.argv.slice(3); - -// Default options: run tests locally (saucelabs false) and use the env variable -// TRAVIS_JOB_NUMBER to get the tunnel identifier, when using saucelabs. -var options = { - saucelabs: false, - tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER -}; - -for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - if (arg == '--saucelabs') { - options.saucelabs = true; - } else if (arg.indexOf('--tunnelIdentifier') == 0) { - options.tunnelIdentifier = arg.split('=')[1]; - } -} - -if (options.saucelabs) { - if (!options.tunnelIdentifier) { - throw 'No tunnel identifier given! Either the TRAVIS_JOB_NUMBER is not ' + - 'set, or you haven\'t passed the --tunnelIdentifier=xxx argument.'; - } - // SauceLabs configuration. - config.sauceUser = process.env.SAUCE_USERNAME; - config.sauceKey = process.env.SAUCE_ACCESS_KEY; - if (!config.sauceKey || !config.sauceUser) { - throw 'The SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables have '+ - ' to be set.'; - } - // Avoid going over the SauceLabs concurrency limit (5). - config.maxSessions = 5; - // List of browsers configurations tested. - var sauceBrowsers = require('./sauce_browsers.json'); - // Configuration for SauceLabs browsers. - config.multiCapabilities = sauceBrowsers.map(function(browser) { - browser['tunnel-identifier'] = options.tunnelIdentifier; - return browser; - }); -} else { - // Configuration for local Chrome and Firefox. - config.seleniumAddress = 'http://localhost:4444/wd/hub'; - config.multiCapabilities = [ - { - 'browserName': 'chrome' - }, - { - 'browserName': 'firefox' - } - ]; -} - -exports.config = config; diff --git a/packages/auth/protractor_spec.js b/packages/auth/protractor_spec.js deleted file mode 100644 index 9fc09df4055..00000000000 --- a/packages/auth/protractor_spec.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var allTests = require('./generated/all_tests'); - -var TEST_SERVER = 'http://localhost:4001'; - -var FLAKY_TEST_RETRIAL = 3; - -describe('Run all Closure unit tests', function() { - /** - * Waits for current tests to be executed. - * @param {!Object} done The function called when the test is finished. - * @param {!Error} fail The function called when an unrecoverable error - * happened during the test. - * @param {?number=} tries The number of trials so far for the current test. - * This is used to retry flaky tests. - */ - var waitForTest = function(done, fail, tries) { - // The default retrial policy. - if (typeof tries === 'undefined') { - tries = FLAKY_TEST_RETRIAL; - } - // executeScript runs the passed method in the "window" context of - // the current test. JSUnit exposes hooks into the test's status through - // the "G_testRunner" global object. - browser.executeScript(function() { - if (window['G_testRunner'] && window['G_testRunner']['isFinished']()) { - return { - isFinished: true, - isSuccess: window['G_testRunner']['isSuccess'](), - report: window['G_testRunner']['getReport']() - }; - } else { - return {'isFinished': false}; - } - }).then(function(status) { - // Tests completed on the page but something failed. Retry a certain - // number of times in case of flakiness. - if (status && status.isFinished && !status.isSuccess && tries > 1) { - // Try again in a few ms. - setTimeout(waitForTest.bind(undefined, done, fail, tries - 1), 300); - } else if (status && status.isFinished) { - done(status); - } else { - // Try again in a few ms. - setTimeout(waitForTest.bind(undefined, done, fail, tries), 300); - } - }, function(err) { - // This can happen if the webdriver had an issue executing the script. - fail(err); - }); - }; - - /** - * Executes the test cases for the file at the given testPath. - * @param {!string} testPath The path of the current test suite to execute. - */ - var executeTest = function(testPath) { - it('runs ' + testPath + ' with success', function(done) { - /** - * Runs the test routines for a given test path and retries up to a - * certain number of times on timeout. - * @param {number} tries The number of times to retry on timeout. - * @param {function()} done The function to run on completion. - */ - var runRoutine = function(tries, done) { - browser.navigate() - .to(TEST_SERVER + '/' + testPath) - .then(function() { - waitForTest(function(status) { - expect(status).toBeSuccess(); - done(); - }, function(err) { - // If browser test execution times out try up to trial times. - if (err.message && - err.message.indexOf('ETIMEDOUT') != -1 && - tries > 0) { - runRoutine(tries - 1, done); - } else { - done.fail(err); - } - }); - }, function(err) { - // If browser test execution times out try up to trial times. - if (err.message && - err.message.indexOf('ETIMEOUT') != -1 && - trial > 0) { - runRoutine(tries - 1, done); - } else { - done.fail(err); - } - }); - }; - // Run test routine. Set timeout retrial to 2 times, eg. test will try - // 2 more times before giving up. - runRoutine(2, done); - }); - }; - - beforeEach(function() { - jasmine.addMatchers({ - // This custom matcher allows for cleaner reports. - toBeSuccess: function() { - return { - // Checks that the status report is successful, otherwise displays - // the report as error message. - compare: function(status) { - return { - pass: status.isSuccess, - message: 'Some test cases failed!\n\n' + status.report - }; - } - }; - } - }); - }); - - // Run all tests. - for (var i = 0; i < allTests.length; i++) { - var testPath = allTests[i]; - executeTest(testPath); - } -}); diff --git a/packages-exp/auth-exp/react-native/package.json b/packages/auth/react-native/package.json similarity index 67% rename from packages-exp/auth-exp/react-native/package.json rename to packages/auth/react-native/package.json index f820f9bfc32..a3fd89bf5f8 100644 --- a/packages-exp/auth-exp/react-native/package.json +++ b/packages/auth/react-native/package.json @@ -1,6 +1,7 @@ { - "name": "@firebase/auth-exp/react-native", + "name": "@firebase/auth/react-native", "description": "A React Native-specific build of the Firebase Auth JS SDK", "browser": "../dist/rn/index.js", + "module": "../dist/rn/index.js", "typings": "../dist/rn/index.rn.d.ts" } \ No newline at end of file diff --git a/packages/auth/rollup.config.js b/packages/auth/rollup.config.js new file mode 100644 index 00000000000..4a547d392b1 --- /dev/null +++ b/packages/auth/rollup.config.js @@ -0,0 +1,172 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import strip from '@rollup/plugin-strip'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import json from '@rollup/plugin-json'; +import typescript from 'typescript'; +import alias from '@rollup/plugin-alias'; +import pkg from './package.json'; + +const deps = Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)); + +/** + * Node has the same entry point as browser, but browser-specific exports + * are turned into either no-ops or errors. See src/platform_node/index.ts for + * more info. This regex tests explicitly ./src/platform_browser so that the + * only impacted file is the main index.ts + */ + const nodeAliasPlugin = alias({ + entries: [ + { + find: /^\.\/src\/platform_browser(\/.*)?$/, + replacement: `./src/platform_node` + } + ] + }); +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + json(), + strip({ + functions: ['debugAssert.*'] + }), + typescriptPlugin({ + typescript + }) +]; + +const es5Builds = [ + /** + * Browser Builds + */ + { + input: { + index: 'index.ts', + internal: 'internal/index.ts' + }, + output: [{ dir: 'dist/esm5', format: 'esm', sourcemap: true }], + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + /** + * Web Worker Build (compiled without DOM) + */ + { + input: 'index.webworker.ts', + output: [{ file: pkg.webworker, format: 'es', sourcemap: true }], + plugins: [ + json(), + strip({ + functions: ['debugAssert.*'] + }), + typescriptPlugin({ + typescript, + compilerOptions: { + lib: [ + // Remove dom after we figure out why navigator stuff doesn't exist + 'dom', + 'es2015', + 'webworker' + ] + } + + }) + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + /** + * Node.js Build + */ + { + input: { + index: 'index.node.ts', + internal: 'internal/index.ts' + }, + output: [{ dir: 'dist/node', format: 'cjs', sourcemap: true }], + plugins: [nodeAliasPlugin, ...es5BuildPlugins], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + /** + * Cordova Builds + */ + { + input: { + index: 'index.cordova.ts', + internal: 'internal/index.ts' + }, + output: [{ dir: 'dist/cordova', format: 'es', sourcemap: true }], + plugins: es5BuildPlugins, + external: id => + [...deps, 'cordova'].some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + /** + * React Native Builds + */ + { + input: { + index: 'index.rn.ts', + internal: 'internal/index.ts' + }, + output: [{ dir: 'dist/rn', format: 'cjs', sourcemap: true }], + plugins: es5BuildPlugins, + external: id => + [...deps, 'react-native'].some( + dep => id === dep || id.startsWith(`${dep}/`) + ) + } +]; + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + json(), + strip({ + functions: ['debugAssert.*'] + }), + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }) +]; + +const es2017Builds = [ + /** + * Browser Builds + */ + { + input: { + index: 'index.ts', + internal: 'internal/index.ts' + }, + output: { + dir: 'dist/esm2017', + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages/auth/sauce_browsers.json b/packages/auth/sauce_browsers.json deleted file mode 100644 index c72162d3e94..00000000000 --- a/packages/auth/sauce_browsers.json +++ /dev/null @@ -1,43 +0,0 @@ -[ - { - "browserName" : "firefox", - "platform" : "OS X 10.9", - "version": "49.0", - "name": "firefox-latest-mac" - }, - { - "browserName" : "chrome", - "platform" : "OS X 10.9", - "timeZone": "Pacific", - "version" : "54.0", - "name": "chrome-latest-mac" - }, - { - "browserName" : "internet explorer", - "version" : "11.0", - "platform" : "Windows 7", - "timeZone": "Pacific", - "name": "ie-11-windows" - }, - { - "browserName" : "internet explorer", - "version" : "10.0", - "timeZone": "Pacific", - "platform" : "Windows 7", - "name": "ie-10-windows" - }, - { - "browserName" : "MicrosoftEdge", - "platform" : "Windows 10", - "timeZone": "Pacific", - "version" : "14.14393", - "name": "edge-14-windows" - }, - { - "browserName" : "safari", - "version" : "10.0", - "platform" : "OS X 10.11", - "timeZone": "Pacific", - "name": "safari-10-mac" - } -] diff --git a/packages-exp/auth-exp/scripts/run_node_tests.ts b/packages/auth/scripts/run_node_tests.ts similarity index 100% rename from packages-exp/auth-exp/scripts/run_node_tests.ts rename to packages/auth/scripts/run_node_tests.ts diff --git a/packages/auth/src/actioncodeinfo.js b/packages/auth/src/actioncodeinfo.js deleted file mode 100644 index c4776cafde7..00000000000 --- a/packages/auth/src/actioncodeinfo.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the firebase.auth.ActionCodeInfo class that is returned - * when calling checkActionCode API and is populated from the server response - * directly. - */ - -goog.provide('fireauth.ActionCodeInfo'); - -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.object'); - - -/** - * Constructs the action code info object which provides metadata corresponding - * to action codes. This includes the type of operation (RESET_PASSWORD, - * VERIFY_EMAIL and RECOVER_EMAIL), the email corresponding to the operation - * and in case of the recover email flow, the old and new email. - * @param {!Object} response The server response for checkActionCode. - * @constructor - */ -fireauth.ActionCodeInfo = function(response) { - var data = {}; - // Original email for email change revocation. - var email = response[fireauth.ActionCodeInfo.ServerFieldName.EMAIL]; - // The new email. - var newEmail = response[fireauth.ActionCodeInfo.ServerFieldName.NEW_EMAIL]; - var operation = - response[fireauth.ActionCodeInfo.ServerFieldName.REQUEST_TYPE]; - // The multi-factor info for revert second factor addition. - var mfaInfo = - fireauth.MultiFactorInfo.fromServerResponse( - response[fireauth.ActionCodeInfo.ServerFieldName.MFA_INFO]); - // Email could be empty only if the request type is EMAIL_SIGNIN or - // VERIFY_AND_CHANGE_EMAIL. - // New email should not be empty if the request type is - // VERIFY_AND_CHANGE_EMAIL. - // Multi-factor info could not be empty if the request type is - // REVERT_SECOND_FACTOR_ADDITION. - if (!operation || - (operation != fireauth.ActionCodeInfo.Operation.EMAIL_SIGNIN && - operation != fireauth.ActionCodeInfo.Operation.VERIFY_AND_CHANGE_EMAIL && - !email) || - (operation == fireauth.ActionCodeInfo.Operation.VERIFY_AND_CHANGE_EMAIL && - !newEmail) || - (operation == - fireauth.ActionCodeInfo.Operation.REVERT_SECOND_FACTOR_ADDITION && - !mfaInfo)) { - // This is internal only. - throw new Error('Invalid checkActionCode response!'); - } - if (operation == fireauth.ActionCodeInfo.Operation.VERIFY_AND_CHANGE_EMAIL) { - data[fireauth.ActionCodeInfo.DataField.FROM_EMAIL] = email || null; - data[fireauth.ActionCodeInfo.DataField.PREVIOUS_EMAIL] = email || null; - data[fireauth.ActionCodeInfo.DataField.EMAIL] = newEmail; - } else { - data[fireauth.ActionCodeInfo.DataField.FROM_EMAIL] = newEmail || null; - data[fireauth.ActionCodeInfo.DataField.PREVIOUS_EMAIL] = newEmail || null; - data[fireauth.ActionCodeInfo.DataField.EMAIL] = email || null; - } - data[fireauth.ActionCodeInfo.DataField.MULTI_FACTOR_INFO] = mfaInfo || null; - fireauth.object.setReadonlyProperty( - this, - fireauth.ActionCodeInfo.PropertyName.OPERATION, - operation); - fireauth.object.setReadonlyProperty( - this, - fireauth.ActionCodeInfo.PropertyName.DATA, - fireauth.object.unsafeCreateReadOnlyCopy(data)); -}; - - -/** - * Firebase Auth Action Code Info operation possible values. - * @enum {string} - */ -fireauth.ActionCodeInfo.Operation = { - PASSWORD_RESET: 'PASSWORD_RESET', - RECOVER_EMAIL: 'RECOVER_EMAIL', - REVERT_SECOND_FACTOR_ADDITION: 'REVERT_SECOND_FACTOR_ADDITION', - EMAIL_SIGNIN: 'EMAIL_SIGNIN', - VERIFY_AND_CHANGE_EMAIL: 'VERIFY_AND_CHANGE_EMAIL', - VERIFY_EMAIL: 'VERIFY_EMAIL' -}; - - -/** - * The checkActionCode endpoint server response field names. - * @enum {string} - */ -fireauth.ActionCodeInfo.ServerFieldName = { - // This is the current email of the account and in email recovery, the email - // to revert to. - EMAIL: 'email', - // The multi-factor info to unenroll for revert second factor addition action. - MFA_INFO: 'mfaInfo', - // For email recovery, this is the new email. - NEW_EMAIL: 'newEmail', - // The action code request type. - REQUEST_TYPE: 'requestType' -}; - - -/** - * The ActionCodeInfo data object field names. - * @enum {string} - */ -fireauth.ActionCodeInfo.DataField = { - EMAIL: 'email', - // This field will be deprecated in favor of PREVIOUS_EMAIL. - FROM_EMAIL: 'fromEmail', - MULTI_FACTOR_INFO: 'multiFactorInfo', - PREVIOUS_EMAIL: 'previousEmail' -}; - - -/** - * The ActionCodeInfo main property names - * @enum {string} - */ -fireauth.ActionCodeInfo.PropertyName = { - DATA: 'data', - OPERATION: 'operation' -}; diff --git a/packages/auth/src/actioncodesettings.js b/packages/auth/src/actioncodesettings.js deleted file mode 100644 index 88f4ca64d8f..00000000000 --- a/packages/auth/src/actioncodesettings.js +++ /dev/null @@ -1,251 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Utility for firebase.auth.ActionCodeSettings and its helper - * functions. - */ - -goog.provide('fireauth.ActionCodeSettings'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); - - -/** - * Defines the action code settings structure used to specify how email action - * links are handled. - * @param {!Object} settingsObj The action code settings object used to - * construct the action code link. - * @constructor @struct @final - */ -fireauth.ActionCodeSettings = function(settingsObj) { - // Validate the settings object passed. - this.initialize_(settingsObj); -}; - - -/** - * Validate the action code settings object. - * @param {!Object} settingsObj The action code settings object to validate. - * @private - */ -fireauth.ActionCodeSettings.prototype.initialize_ = function(settingsObj) { - // URL should be required. - var continueUrl = settingsObj[fireauth.ActionCodeSettings.RawField.URL]; - if (typeof continueUrl === 'undefined') { - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_CONTINUE_URI); - } else if (typeof continueUrl !== 'string' || - (typeof continueUrl === 'string' && !continueUrl.length)) { - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_CONTINUE_URI); - } - /** @const @private {string} The continue URL. */ - this.continueUrl_ = /** @type {string} */ (continueUrl); - - // Validate Android parameters. - /** @private {?string} The Android package name. */ - this.apn_ = null; - /** @private {?string} The Android minimum version. */ - this.amv_ = null; - /** @private {boolean} Whether to install the Android app. */ - this.installApp_ = false; - var androidSettings = - settingsObj[fireauth.ActionCodeSettings.RawField.ANDROID]; - if (androidSettings && typeof androidSettings === 'object') { - var apn = androidSettings[ - fireauth.ActionCodeSettings.AndroidRawField.PACKAGE_NAME]; - var installApp = androidSettings[ - fireauth.ActionCodeSettings.AndroidRawField.INSTALL_APP]; - var amv = androidSettings[ - fireauth.ActionCodeSettings.AndroidRawField.MINIMUM_VERSION]; - if (typeof apn === 'string' && apn.length) { - this.apn_ = /** @type {string} */ (apn); - if (typeof installApp !== 'undefined' && - typeof installApp !== 'boolean') { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.AndroidRawField.INSTALL_APP + - ' property must be a boolean when specified.'); - } - this.installApp_ = !!installApp; - if (typeof amv !== 'undefined' && - (typeof amv !== 'string' || - (typeof amv === 'string' && !amv.length))) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.AndroidRawField.MINIMUM_VERSION + - ' property must be a non empty string when specified.'); - } - this.amv_ = /** @type {?string}*/ (amv || null); - } else if (typeof apn !== 'undefined') { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.AndroidRawField.PACKAGE_NAME + - ' property must be a non empty string when specified.'); - } else if (typeof installApp !== 'undefined' || - typeof amv !== 'undefined') { - // If installApp or amv specified with no valid APN, fail quickly. - throw new fireauth.AuthError( - fireauth.authenum.Error.MISSING_ANDROID_PACKAGE_NAME); - } - } else if (typeof androidSettings !== 'undefined') { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.RawField.ANDROID + - ' property must be a non null object when specified.'); - } - - // Validate iOS parameters. - /** @private {?string} The iOS bundle ID. */ - this.ibi_ = null; - var iosSettings = settingsObj[fireauth.ActionCodeSettings.RawField.IOS]; - if (iosSettings && typeof iosSettings === 'object') { - var ibi = iosSettings[ - fireauth.ActionCodeSettings.IosRawField.BUNDLE_ID]; - if (typeof ibi === 'string' && ibi.length) { - this.ibi_ = /** @type {string}*/ (ibi); - } else if (typeof ibi !== 'undefined') { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.IosRawField.BUNDLE_ID + - ' property must be a non empty string when specified.'); - } - } else if (typeof iosSettings !== 'undefined') { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.RawField.IOS + - ' property must be a non null object when specified.'); - } - - // Validate canHandleCodeInApp. - var canHandleCodeInApp = - settingsObj[fireauth.ActionCodeSettings.RawField.HANDLE_CODE_IN_APP]; - if (typeof canHandleCodeInApp !== 'undefined' && - typeof canHandleCodeInApp !== 'boolean') { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.RawField.HANDLE_CODE_IN_APP + - ' property must be a boolean when specified.'); - } - /** @const @private {boolean} Whether the code can be handled in app. */ - this.canHandleCodeInApp_ = !!canHandleCodeInApp; - - // Validate dynamicLinkDomain. - var dynamicLinkDomain = settingsObj[ - fireauth.ActionCodeSettings.RawField.DYNAMIC_LINK_DOMAIN]; - if (typeof dynamicLinkDomain !== 'undefined' && - (typeof dynamicLinkDomain !== 'string' || - (typeof dynamicLinkDomain === 'string' && - !dynamicLinkDomain.length))) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.RawField.DYNAMIC_LINK_DOMAIN + - ' property must be a non empty string when specified.'); - } - /** @const @private {?string} The FDL domain. */ - this.dynamicLinkDomain_ = dynamicLinkDomain || null; -}; - - -/** - * Action code settings backend request field names. - * @enum {string} - */ -fireauth.ActionCodeSettings.RequestField = { - ANDROID_INSTALL_APP: 'androidInstallApp', - ANDROID_MINIMUM_VERSION: 'androidMinimumVersion', - ANDROID_PACKAGE_NAME: 'androidPackageName', - CAN_HANDLE_CODE_IN_APP: 'canHandleCodeInApp', - CONTINUE_URL: 'continueUrl', - DYNAMIC_LINK_DOMAIN: 'dynamicLinkDomain', - IOS_BUNDLE_ID: 'iOSBundleId' -}; - - -/** - * Action code settings raw field names. - * @enum {string} - */ -fireauth.ActionCodeSettings.RawField = { - ANDROID: 'android', - DYNAMIC_LINK_DOMAIN: 'dynamicLinkDomain', - HANDLE_CODE_IN_APP: 'handleCodeInApp', - IOS: 'iOS', - URL: 'url' -}; - - -/** - * Action code settings raw Android raw field names. - * @enum {string} - */ -fireauth.ActionCodeSettings.AndroidRawField = { - INSTALL_APP: 'installApp', - MINIMUM_VERSION: 'minimumVersion', - PACKAGE_NAME: 'packageName' -}; - - -/** - * Action code settings raw iOS raw field names. - * @enum {string} - */ -fireauth.ActionCodeSettings.IosRawField = { - BUNDLE_ID: 'bundleId' -}; - - -/** - * Builds and returns the backend request for the passed action code settings. - * @return {!Object} The constructed backend request populated with the action - * code settings parameters. - */ -fireauth.ActionCodeSettings.prototype.buildRequest = function() { - // Construct backend request. - var request = {}; - request[fireauth.ActionCodeSettings.RequestField.CONTINUE_URL] = - this.continueUrl_; - request[fireauth.ActionCodeSettings.RequestField.CAN_HANDLE_CODE_IN_APP] = - this.canHandleCodeInApp_; - request[fireauth.ActionCodeSettings.RequestField.ANDROID_PACKAGE_NAME] = - this.apn_; - if (this.apn_) { - request[fireauth.ActionCodeSettings.RequestField.ANDROID_MINIMUM_VERSION] = - this.amv_; - request[fireauth.ActionCodeSettings.RequestField.ANDROID_INSTALL_APP] = - this.installApp_; - } - request[fireauth.ActionCodeSettings.RequestField.IOS_BUNDLE_ID] = this.ibi_; - request[fireauth.ActionCodeSettings.RequestField.DYNAMIC_LINK_DOMAIN] = - this.dynamicLinkDomain_; - // Remove null fields. - for (var key in request) { - if (request[key] === null) { - delete request[key]; - } - } - return request; -}; - - -/** - * Returns the canHandleCodeInApp setting of ActionCodeSettings. - * @return {boolean} Whether the code can be handled in app. - */ -fireauth.ActionCodeSettings.prototype.canHandleCodeInApp = function() { - return this.canHandleCodeInApp_; -}; diff --git a/packages/auth/src/actioncodeurl.js b/packages/auth/src/actioncodeurl.js deleted file mode 100644 index 80041b6fa00..00000000000 --- a/packages/auth/src/actioncodeurl.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines firebase.auth.ActionCodeURL class which is the utility - * to parse action code URLs. - */ - -goog.provide('fireauth.ActionCodeURL'); - -goog.require('fireauth.ActionCodeInfo'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.object'); -goog.require('goog.Uri'); - - -/** - * The utility class to help parse action code URLs used for out of band email - * flows such as password reset, email verification, email link sign in, etc. - * @param {string} actionLink The action link string. - * @constructor - */ -fireauth.ActionCodeURL = function(actionLink) { - var uri = goog.Uri.parse(actionLink); - var apiKey = uri.getParameterValue( - fireauth.ActionCodeURL.QueryField.API_KEY) || null; - var code = uri.getParameterValue( - fireauth.ActionCodeURL.QueryField.CODE) || null; - var mode = uri.getParameterValue( - fireauth.ActionCodeURL.QueryField.MODE) || null; - var operation = fireauth.ActionCodeURL.getOperation(mode); - // Validate API key, code and mode. - if (!apiKey || !code || !operation) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeURL.QueryField.API_KEY + ', ' + - fireauth.ActionCodeURL.QueryField.CODE + 'and ' + - fireauth.ActionCodeURL.QueryField.MODE + - ' are required in a valid action code URL.'); - } - fireauth.object.setReadonlyProperties(this, { - 'apiKey': apiKey, - 'operation': operation, - 'code': code, - 'continueUrl': uri.getParameterValue( - fireauth.ActionCodeURL.QueryField.CONTINUE_URL) || null, - 'languageCode': uri.getParameterValue( - fireauth.ActionCodeURL.QueryField.LANGUAGE_CODE) || null, - 'tenantId': uri.getParameterValue( - fireauth.ActionCodeURL.QueryField.TENANT_ID) || null - }); -}; - -/** - * Enums for fields in URL query string. - * @enum {string} - */ -fireauth.ActionCodeURL.QueryField = { - API_KEY: 'apiKey', - CODE: 'oobCode', - CONTINUE_URL: 'continueUrl', - LANGUAGE_CODE: 'languageCode', - MODE: 'mode', - TENANT_ID: 'tenantId' -}; - - -/** - * Map of mode string to Action Code Info operation. - * @const @private {!Object} - */ -fireauth.ActionCodeURL.ModeToOperationMap_ = { - 'recoverEmail': fireauth.ActionCodeInfo.Operation.RECOVER_EMAIL, - 'resetPassword': fireauth.ActionCodeInfo.Operation.PASSWORD_RESET, - 'revertSecondFactorAddition': - fireauth.ActionCodeInfo.Operation.REVERT_SECOND_FACTOR_ADDITION, - 'signIn': fireauth.ActionCodeInfo.Operation.EMAIL_SIGNIN, - 'verifyAndChangeEmail': - fireauth.ActionCodeInfo.Operation.VERIFY_AND_CHANGE_EMAIL, - 'verifyEmail': fireauth.ActionCodeInfo.Operation.VERIFY_EMAIL -}; - - -/** - * Maps the mode string in action code URL to Action Code Info operation. - * @param {?string} mode The mode string in the URL. - * @return {?fireauth.ActionCodeInfo.Operation} - */ -fireauth.ActionCodeURL.getOperation = function(mode) { - if (!mode) { - return null; - } - return fireauth.ActionCodeURL.ModeToOperationMap_[mode] || null; - -}; - - -/** - * Returns an ActionCodeURL instance if the link is valid, otherwise null. - * @param {string} actionLink The action code link string. - * @return {?fireauth.ActionCodeURL} - */ -fireauth.ActionCodeURL.parseLink = function(actionLink) { - try { - return new fireauth.ActionCodeURL(actionLink); - } catch(e) { - return null; - } -}; diff --git a/packages/auth/src/additionaluserinfo.js b/packages/auth/src/additionaluserinfo.js deleted file mode 100644 index 3b5af3798db..00000000000 --- a/packages/auth/src/additionaluserinfo.js +++ /dev/null @@ -1,265 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines all the fireauth additional user info interfaces, - * implementations and subclasses. - */ - -goog.provide('fireauth.AdditionalUserInfo'); -goog.provide('fireauth.FacebookAdditionalUserInfo'); -goog.provide('fireauth.FederatedAdditionalUserInfo'); -goog.provide('fireauth.GenericAdditionalUserInfo'); -goog.provide('fireauth.GithubAdditionalUserInfo'); -goog.provide('fireauth.GoogleAdditionalUserInfo'); -goog.provide('fireauth.TwitterAdditionalUserInfo'); - -goog.require('fireauth.IdToken'); -goog.require('fireauth.idp'); -goog.require('fireauth.object'); -goog.require('fireauth.util'); - - -/** - * The interface that represents additional user info. - * @interface - */ -fireauth.AdditionalUserInfo = function() {}; - - -/** - * Constructs the corresponding additional user info for the backend - * verifyAssertion response. - * @param {?Object|undefined} resp The backend verifyAssertion, - * verifyPhoneNumber or verifyPassword/setAccountInfo response. - * @return {?fireauth.AdditionalUserInfo} The fireauth.AdditionalUserInfo - * instance. - */ -fireauth.AdditionalUserInfo.fromPlainObject = function(resp) { - var factory = {}; - factory[fireauth.idp.ProviderId.FACEBOOK] = - fireauth.FacebookAdditionalUserInfo; - factory[fireauth.idp.ProviderId.GOOGLE] = - fireauth.GoogleAdditionalUserInfo; - factory[fireauth.idp.ProviderId.GITHUB] = - fireauth.GithubAdditionalUserInfo; - factory[fireauth.idp.ProviderId.TWITTER] = - fireauth.TwitterAdditionalUserInfo; - // Provider ID and UID are required. - var providerId = - resp && - resp[fireauth.AdditionalUserInfo.VerifyAssertionField.PROVIDER_ID]; - try { - // Provider ID already present. - if (providerId) { - if (factory[providerId]) { - // 1st class supported federated providers. - return new factory[providerId](resp); - } else { - // Generic federated providers. - return new fireauth.FederatedAdditionalUserInfo( - /** @type {!Object} */ (resp)); - } - } else if (typeof resp[fireauth.AdditionalUserInfo.VerifyAssertionField - .ID_TOKEN] !== 'undefined') { - // For all other ID token responses with no providerId, get the required - // providerId from the ID token itself. - return new fireauth.GenericAdditionalUserInfo( - /** @type {!Object} */ (resp)); - } - } catch (e) { - // Do nothing, null will be returned. - } - return null; -}; - - - -/** - * verifyAssertion response additional user info fields. - * @enum {string} - */ -fireauth.AdditionalUserInfo.VerifyAssertionField = { - ID_TOKEN: 'idToken', - IS_NEW_USER: 'isNewUser', - KIND: 'kind', - PROVIDER_ID: 'providerId', - RAW_USER_INFO: 'rawUserInfo', - SCREEN_NAME: 'screenName' -}; - - -/** - * Constructs a generic additional user info object from the backend - * verifyPhoneNumber and verifyPassword provider response. - * @param {!Object} info The verifyPhoneNumber/verifyPassword/setAccountInfo - * response data object. - * @constructor - * @implements {fireauth.AdditionalUserInfo} - */ -fireauth.GenericAdditionalUserInfo = function(info) { - // Federated provider profile data. - var providerId = - info[fireauth.AdditionalUserInfo.VerifyAssertionField.PROVIDER_ID]; - // Try to get providerId from the ID token if available. - if (!providerId && - info[fireauth.AdditionalUserInfo.VerifyAssertionField.ID_TOKEN]) { - // verifyPassword/setAccountInfo and verifyPhoneNumber return an ID token - // but no providerId. Get providerId from the token itself. - // isNewUser will be returned for verifyPhoneNumber. - var idToken = fireauth.IdToken.parse( - info[fireauth.AdditionalUserInfo.VerifyAssertionField.ID_TOKEN]); - if (idToken && idToken.getProviderId()) { - providerId = idToken.getProviderId(); - } - } - if (!providerId) { - // This is internal only. - throw new Error('Invalid additional user info!'); - } - // For custom token and anonymous token, set provider ID to null. - if (providerId == fireauth.idp.ProviderId.ANONYMOUS || - providerId == fireauth.idp.ProviderId.CUSTOM) { - providerId = null; - } - // Check whether user is new. Temporary Solution since backend does not return - // isNewUser field for SignupNewUserResponse. - var isNewUser = false; - if (typeof info[fireauth.AdditionalUserInfo.VerifyAssertionField.IS_NEW_USER] - !== 'undefined') { - isNewUser = - !!info[fireauth.AdditionalUserInfo.VerifyAssertionField.IS_NEW_USER]; - } else if (info[fireauth.AdditionalUserInfo.VerifyAssertionField.KIND] - === 'identitytoolkit#SignupNewUserResponse') { - //For SignupNewUserResponse, always set isNewUser to true. - isNewUser = true; - } - // Set required providerId. - fireauth.object.setReadonlyProperty(this, 'providerId', providerId); - // Set read-only isNewUser property. - fireauth.object.setReadonlyProperty(this, 'isNewUser', isNewUser); -}; - - -/** - * Constructs a federated additional user info object from the backend - * verifyAssertion federated provider response. - * @param {!Object} info The verifyAssertion response data object. - * @constructor - * @extends {fireauth.GenericAdditionalUserInfo} - */ -fireauth.FederatedAdditionalUserInfo = function(info) { - fireauth.FederatedAdditionalUserInfo.base(this, 'constructor', info); - // Federated provider profile data. - // This structure will also be used for generic IdPs. - var profile = fireauth.util.parseJSON( - info[fireauth.AdditionalUserInfo.VerifyAssertionField.RAW_USER_INFO] || - '{}'); - // Set read-only profile property. - fireauth.object.setReadonlyProperty( - this, - 'profile', - fireauth.object.unsafeCreateReadOnlyCopy(profile || {})); -}; -goog.inherits( - fireauth.FederatedAdditionalUserInfo, fireauth.GenericAdditionalUserInfo); - - -/** - * Constructs a Facebook additional user info object from the backend - * verifyAssertion Facebook provider response. - * @param {!Object} info The verifyAssertion response data object. - * @constructor - * @extends {fireauth.FederatedAdditionalUserInfo} - */ -fireauth.FacebookAdditionalUserInfo = function(info) { - fireauth.FacebookAdditionalUserInfo.base(this, 'constructor', info); - // This should not happen as this object is initialized via fromPlainObject. - if (this['providerId'] != fireauth.idp.ProviderId.FACEBOOK) { - throw new Error('Invalid provider ID!'); - } -}; -goog.inherits( - fireauth.FacebookAdditionalUserInfo, fireauth.FederatedAdditionalUserInfo); - - - -/** - * Constructs a GitHub additional user info object from the backend - * verifyAssertion GitHub provider response. - * @param {!Object} info The verifyAssertion response data object. - * @constructor - * @extends {fireauth.FederatedAdditionalUserInfo} - */ -fireauth.GithubAdditionalUserInfo = function(info) { - fireauth.GithubAdditionalUserInfo.base(this, 'constructor', info); - // This should not happen as this object is initialized via fromPlainObject. - if (this['providerId'] != fireauth.idp.ProviderId.GITHUB) { - throw new Error('Invalid provider ID!'); - } - // GitHub username. - fireauth.object.setReadonlyProperty( - this, - 'username', - (this['profile'] && this['profile']['login']) || null); -}; -goog.inherits( - fireauth.GithubAdditionalUserInfo, fireauth.FederatedAdditionalUserInfo); - - - -/** - * Constructs a Google additional user info object from the backend - * verifyAssertion Google provider response. - * @param {!Object} info The verifyAssertion response data object. - * @constructor - * @extends {fireauth.FederatedAdditionalUserInfo} - */ -fireauth.GoogleAdditionalUserInfo = function(info) { - fireauth.GoogleAdditionalUserInfo.base(this, 'constructor', info); - // This should not happen as this object is initialized via fromPlainObject. - if (this['providerId'] != fireauth.idp.ProviderId.GOOGLE) { - throw new Error('Invalid provider ID!'); - } -}; -goog.inherits( - fireauth.GoogleAdditionalUserInfo, fireauth.FederatedAdditionalUserInfo); - - - -/** - * Constructs a Twitter additional user info object from the backend - * verifyAssertion Twitter provider response. - * @param {!Object} info The verifyAssertion response data object. - * @constructor - * @extends {fireauth.FederatedAdditionalUserInfo} - */ -fireauth.TwitterAdditionalUserInfo = function(info) { - fireauth.TwitterAdditionalUserInfo.base(this, 'constructor', info); - // This should not happen as this object is initialized via fromPlainObject. - if (this['providerId'] != fireauth.idp.ProviderId.TWITTER) { - throw new Error('Invalid provider ID!'); - } - // Twitter user name. - fireauth.object.setReadonlyProperty( - this, - 'username', - info[fireauth.AdditionalUserInfo.VerifyAssertionField.SCREEN_NAME] || - null); -}; -goog.inherits( - fireauth.TwitterAdditionalUserInfo, fireauth.FederatedAdditionalUserInfo); diff --git a/packages-exp/auth-exp/src/api/account_management/account.test.ts b/packages/auth/src/api/account_management/account.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/account_management/account.test.ts rename to packages/auth/src/api/account_management/account.test.ts diff --git a/packages-exp/auth-exp/src/api/account_management/account.ts b/packages/auth/src/api/account_management/account.ts similarity index 100% rename from packages-exp/auth-exp/src/api/account_management/account.ts rename to packages/auth/src/api/account_management/account.ts diff --git a/packages-exp/auth-exp/src/api/account_management/email_and_password.test.ts b/packages/auth/src/api/account_management/email_and_password.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/account_management/email_and_password.test.ts rename to packages/auth/src/api/account_management/email_and_password.test.ts diff --git a/packages-exp/auth-exp/src/api/account_management/email_and_password.ts b/packages/auth/src/api/account_management/email_and_password.ts similarity index 100% rename from packages-exp/auth-exp/src/api/account_management/email_and_password.ts rename to packages/auth/src/api/account_management/email_and_password.ts diff --git a/packages-exp/auth-exp/src/api/account_management/mfa.test.ts b/packages/auth/src/api/account_management/mfa.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/account_management/mfa.test.ts rename to packages/auth/src/api/account_management/mfa.test.ts diff --git a/packages-exp/auth-exp/src/api/account_management/mfa.ts b/packages/auth/src/api/account_management/mfa.ts similarity index 100% rename from packages-exp/auth-exp/src/api/account_management/mfa.ts rename to packages/auth/src/api/account_management/mfa.ts diff --git a/packages-exp/auth-exp/src/api/account_management/profile.test.ts b/packages/auth/src/api/account_management/profile.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/account_management/profile.test.ts rename to packages/auth/src/api/account_management/profile.test.ts diff --git a/packages-exp/auth-exp/src/api/account_management/profile.ts b/packages/auth/src/api/account_management/profile.ts similarity index 100% rename from packages-exp/auth-exp/src/api/account_management/profile.ts rename to packages/auth/src/api/account_management/profile.ts diff --git a/packages-exp/auth-exp/src/api/authentication/create_auth_uri.test.ts b/packages/auth/src/api/authentication/create_auth_uri.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/create_auth_uri.test.ts rename to packages/auth/src/api/authentication/create_auth_uri.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/create_auth_uri.ts b/packages/auth/src/api/authentication/create_auth_uri.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/create_auth_uri.ts rename to packages/auth/src/api/authentication/create_auth_uri.ts diff --git a/packages-exp/auth-exp/src/api/authentication/custom_token.test.ts b/packages/auth/src/api/authentication/custom_token.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/custom_token.test.ts rename to packages/auth/src/api/authentication/custom_token.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/custom_token.ts b/packages/auth/src/api/authentication/custom_token.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/custom_token.ts rename to packages/auth/src/api/authentication/custom_token.ts diff --git a/packages-exp/auth-exp/src/api/authentication/email_and_password.test.ts b/packages/auth/src/api/authentication/email_and_password.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/email_and_password.test.ts rename to packages/auth/src/api/authentication/email_and_password.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/email_and_password.ts b/packages/auth/src/api/authentication/email_and_password.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/email_and_password.ts rename to packages/auth/src/api/authentication/email_and_password.ts diff --git a/packages-exp/auth-exp/src/api/authentication/email_link.test.ts b/packages/auth/src/api/authentication/email_link.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/email_link.test.ts rename to packages/auth/src/api/authentication/email_link.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/email_link.ts b/packages/auth/src/api/authentication/email_link.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/email_link.ts rename to packages/auth/src/api/authentication/email_link.ts diff --git a/packages-exp/auth-exp/src/api/authentication/idp.test.ts b/packages/auth/src/api/authentication/idp.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/idp.test.ts rename to packages/auth/src/api/authentication/idp.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/idp.ts b/packages/auth/src/api/authentication/idp.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/idp.ts rename to packages/auth/src/api/authentication/idp.ts diff --git a/packages-exp/auth-exp/src/api/authentication/mfa.test.ts b/packages/auth/src/api/authentication/mfa.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/mfa.test.ts rename to packages/auth/src/api/authentication/mfa.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/mfa.ts b/packages/auth/src/api/authentication/mfa.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/mfa.ts rename to packages/auth/src/api/authentication/mfa.ts diff --git a/packages-exp/auth-exp/src/api/authentication/recaptcha.test.ts b/packages/auth/src/api/authentication/recaptcha.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/recaptcha.test.ts rename to packages/auth/src/api/authentication/recaptcha.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/recaptcha.ts b/packages/auth/src/api/authentication/recaptcha.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/recaptcha.ts rename to packages/auth/src/api/authentication/recaptcha.ts diff --git a/packages-exp/auth-exp/src/api/authentication/sign_up.test.ts b/packages/auth/src/api/authentication/sign_up.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/sign_up.test.ts rename to packages/auth/src/api/authentication/sign_up.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/sign_up.ts b/packages/auth/src/api/authentication/sign_up.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/sign_up.ts rename to packages/auth/src/api/authentication/sign_up.ts diff --git a/packages-exp/auth-exp/src/api/authentication/sms.test.ts b/packages/auth/src/api/authentication/sms.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/sms.test.ts rename to packages/auth/src/api/authentication/sms.test.ts diff --git a/packages-exp/auth-exp/src/api/authentication/sms.ts b/packages/auth/src/api/authentication/sms.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/sms.ts rename to packages/auth/src/api/authentication/sms.ts diff --git a/packages-exp/auth-exp/src/api/authentication/token.test.ts b/packages/auth/src/api/authentication/token.test.ts similarity index 98% rename from packages-exp/auth-exp/src/api/authentication/token.test.ts rename to packages/auth/src/api/authentication/token.test.ts index 6cb58abe9d8..af0ef19d762 100644 --- a/packages-exp/auth-exp/src/api/authentication/token.test.ts +++ b/packages/auth/src/api/authentication/token.test.ts @@ -25,7 +25,7 @@ import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; import { ServerError } from '../errors'; import { Endpoint, requestStsToken } from './token'; -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; import { _getBrowserName } from '../../core/util/browser'; use(chaiAsPromised); diff --git a/packages-exp/auth-exp/src/api/authentication/token.ts b/packages/auth/src/api/authentication/token.ts similarity index 100% rename from packages-exp/auth-exp/src/api/authentication/token.ts rename to packages/auth/src/api/authentication/token.ts diff --git a/packages-exp/auth-exp/src/api/errors.ts b/packages/auth/src/api/errors.ts similarity index 100% rename from packages-exp/auth-exp/src/api/errors.ts rename to packages/auth/src/api/errors.ts diff --git a/packages-exp/auth-exp/src/api/index.test.ts b/packages/auth/src/api/index.test.ts similarity index 99% rename from packages-exp/auth-exp/src/api/index.test.ts rename to packages/auth/src/api/index.test.ts index 7bbb5dd8314..990cde7cbb0 100644 --- a/packages-exp/auth-exp/src/api/index.test.ts +++ b/packages/auth/src/api/index.test.ts @@ -38,7 +38,7 @@ import { _addTidIfNecessary } from './'; import { ServerError } from './errors'; -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; import { _getBrowserName } from '../core/util/browser'; use(sinonChai); diff --git a/packages-exp/auth-exp/src/api/index.ts b/packages/auth/src/api/index.ts similarity index 100% rename from packages-exp/auth-exp/src/api/index.ts rename to packages/auth/src/api/index.ts diff --git a/packages-exp/auth-exp/src/api/project_config/get_project_config.test.ts b/packages/auth/src/api/project_config/get_project_config.test.ts similarity index 100% rename from packages-exp/auth-exp/src/api/project_config/get_project_config.test.ts rename to packages/auth/src/api/project_config/get_project_config.test.ts diff --git a/packages-exp/auth-exp/src/api/project_config/get_project_config.ts b/packages/auth/src/api/project_config/get_project_config.ts similarity index 100% rename from packages-exp/auth-exp/src/api/project_config/get_project_config.ts rename to packages/auth/src/api/project_config/get_project_config.ts diff --git a/packages/auth/src/args.js b/packages/auth/src/args.js deleted file mode 100644 index ed6547e6c22..00000000000 --- a/packages/auth/src/args.js +++ /dev/null @@ -1,650 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Provides function argument validation for third-party calls - * that cannot be validated with Closure compiler. - */ - -goog.provide('fireauth.args'); -goog.provide('fireauth.args.Argument'); - -goog.require('fireauth.Auth'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthUser'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.authenum.Error'); - - -/** - * Represents an argument to a function. Fields: - *
    - *
  • name: A label for the argument. For example, the names of the arguments - * to a signIn() function might be "email" and "password". - *
  • typeLabel: A label for the expected type of the argument, starting with - * an article, for example, "an object" or "a valid credential". - *
  • optional: Whether or not this argument is optional. Optional arguments - * cannot come after non-optional arguments in the input to validate(). - *
  • validator: A function that takes the passed value of this argument - * and returns whether the value is valid or not. - *
- * @typedef {{ - * name: string, - * typeLabel: string, - * optional: boolean, - * validator: function (*) : boolean, - * }} - */ -fireauth.args.Argument; - - -/** - * Validates the arguments to a method call and throws an error if invalid. This - * can be used to validate external calls where the Closure compiler cannot - * detect errors. - * - * Example usage: - * function greet(recipient, opt_useFormalLanguage) { - * fireauth.args.validate('greet', [ - * fireauth.args.string('recipient'), - * fireauth.args.bool('opt_useFormalLanguage', true) - * ], arguments); - * if (opt_useFormalLanguage) { - * console.log('Good day, ' + recipient + '.'); - * } else { - * console.log('Wassup, ' + recipient + '?'); - * } - * } - * greet('Mr. Manager', true); // Prints 'Good day, Mr. Manager.' - * greet('Billy Bob'); // Prints 'Wassup, Billy Bob?' - * greet(133); // Throws 'greet failed: First argument "recipient" must be a - * // valid string.' - * greet(); // Throws 'greet failed: Expected 1-2 arguments but got 0.' - * greet('Mr. Manager', true, 'ohno'); // Throws 'greet failed: Expected 1-2 - * // arguments but got 3.' - * - * This can also be used to validate setters by passing an additional true - * argument to fireauth.args.validate. This modifies the error message to be - * relevant for that setter. - * - * @param {string} apiName The name of the method being called, to display in - * the error message for debugging purposes. - * @param {!Array} expected The expected arguments. - * @param {!IArrayLike} actual The arguments object of the function whose - * parameters we want to validate. - * @param {boolean=} opt_isSetter Whether the function is a setter which takes - * a single argument. - */ -fireauth.args.validate = function(apiName, expected, actual, opt_isSetter) { - // Convert the arguments object into a real array. - var actualAsArray = Array.prototype.slice.call(actual); - var errorMessage = fireauth.args.validateAndGetMessage_( - expected, actualAsArray, opt_isSetter); - if (errorMessage) { - throw new fireauth.AuthError(fireauth.authenum.Error.ARGUMENT_ERROR, - apiName + ' failed: ' + errorMessage); - } -}; - - -/** - * @param {!Array} expected - * @param {!Array<*>} actual - * @param {boolean=} opt_isSetter Whether the function is a setter which takes - * a single argument. - * @return {?string} The error message if there is an error, or otherwise - * null. - * @private - */ -fireauth.args.validateAndGetMessage_ = - function(expected, actual, opt_isSetter) { - var minNumArgs = fireauth.args.calcNumRequiredArgs_(expected); - var maxNumArgs = expected.length; - if (actual.length < minNumArgs || maxNumArgs < actual.length) { - return fireauth.args.makeLengthError_(minNumArgs, maxNumArgs, - actual.length); - } - - for (var i = 0; i < actual.length; i++) { - // Argument is optional and undefined is explicitly passed. - var optionalUndefined = expected[i].optional && actual[i] === undefined; - // Check if invalid argument and the argument is not optional with undefined - // passed. - if (!expected[i].validator(actual[i]) && !optionalUndefined) { - return fireauth.args.makeErrorAtPosition_(i, expected[i], opt_isSetter); - } - } - - return null; -}; - - -/** - * @param {!Array} expected - * @return {number} The number of required arguments. - * @private - */ -fireauth.args.calcNumRequiredArgs_ = function(expected) { - var numRequiredArgs = 0; - var isOptionalSection = false; - for (var i = 0; i < expected.length; i++) { - if (expected[i].optional) { - isOptionalSection = true; - } else { - if (isOptionalSection) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'Argument validator encountered a required argument after an ' + - 'optional argument.'); - } - numRequiredArgs++; - } - } - return numRequiredArgs; -}; - - -/** - * @param {number} min The minimum number of arguments to the function, - * inclusive. - * @param {number} max The maximum number of arguments to the function, - * inclusive. - * @param {number} actual The actual number of arguments received. - * @return {string} The error message. - * @private - */ -fireauth.args.makeLengthError_ = function(min, max, actual) { - var numExpectedString; - if (min == max) { - if (min == 1) { - numExpectedString = '1 argument'; - } else { - numExpectedString = min + ' arguments'; - } - } else { - numExpectedString = min + '-' + max + ' arguments'; - } - return 'Expected ' + numExpectedString + ' but got ' + actual + '.'; -}; - - -/** - * @param {number} position The position at which there was an error. - * @param {!fireauth.args.Argument} expectedType The expected type of the - * argument, which was violated. - * @param {boolean=} opt_isSetter Whether the function is a setter which takes - * a single argument. - * @return {string} The error message. - * @private - */ -fireauth.args.makeErrorAtPosition_ = - function(position, expectedType, opt_isSetter) { - var ordinal = fireauth.args.makeOrdinal_(position); - var argName = expectedType.name ? - fireauth.args.quoteString_(expectedType.name) + ' ' : ''; - // Add support to setters for readable/writable properties which take a - // required single argument. - var errorPrefix = !!opt_isSetter ? '' : ordinal + ' argument '; - return errorPrefix + argName + 'must be ' + - expectedType.typeLabel + '.'; -}; - - -/** @private {!Array} The first few ordinal numbers. */ -fireauth.args.ORDINAL_NUMBERS_ = ['First', 'Second', 'Third', 'Fourth', - 'Fifth', 'Sixth', 'Seventh', 'Eighth', 'Ninth']; - - -/** - * @param {number} cardinal An integer. - * @return {string} The integer converted to an ordinal number, starting at - * "First". That is, makeOrdinal_(0) returns "First" and makeOrdinal_(1) - * returns "Second", etc. - * @private - */ -fireauth.args.makeOrdinal_ = function(cardinal) { - // We only support the first few ordinal numbers. We could provide a more - // robust solution, but it is unlikely that a function would need more than - // nine arguments. - if (cardinal < 0 || cardinal >= fireauth.args.ORDINAL_NUMBERS_.length) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'Argument validator received an unsupported number of arguments.'); - } - return fireauth.args.ORDINAL_NUMBERS_[cardinal]; -}; - - -/** - * Specifies a string argument. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.string = function(opt_name, opt_optional) { - return { - name: opt_name || '', - typeLabel: 'a valid string', - optional: !!opt_optional, - validator: x => typeof x === 'string' - }; -}; - - -/** - * Specifies a boolean argument. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.bool = function(opt_name, opt_optional) { - return { - name: opt_name || '', - typeLabel: 'a boolean', - optional: !!opt_optional, - validator: x => typeof x === 'boolean' - }; -}; - - -/** - * Specifies a number argument. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.number = function(opt_name, opt_optional) { - return { - name: opt_name || '', - typeLabel: 'a valid number', - optional: !!opt_optional, - validator: x => typeof x === 'number' - }; -}; - - -/** - * Specifies an object argument. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.object = function(opt_name, opt_optional) { - return { - name: opt_name || '', - typeLabel: 'a valid object', - optional: !!opt_optional, - validator: goog.isObject - }; -}; - - -/** - * Specifies a function argument. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.func = function(opt_name, opt_optional) { - return { - name: opt_name || '', - typeLabel: 'a function', - optional: !!opt_optional, - validator: x => typeof x === 'function' - }; -}; - - -/** - * Specifies a null argument. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.null = function(opt_name, opt_optional) { - return { - name: opt_name || '', - typeLabel: 'null', - optional: !!opt_optional, - validator: x => x === null - }; -}; - - -/** - * Specifies an HTML element argument. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.element = function(opt_name, opt_optional) { - return /** @type {!fireauth.args.Argument} */ ({ - name: opt_name || '', - typeLabel: 'an HTML element', - optional: !!opt_optional, - validator: /** @type {function(!Element) : boolean} */ ( - function(element) { - return !!(element && element instanceof Element); - }) - }); -}; - - -/** - * Specifies an instance of Firebase Auth. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.firebaseAuth = function(opt_optional) { - return /** @type {!fireauth.args.Argument} */ ({ - name: 'auth', - typeLabel: 'an instance of Firebase Auth', - optional: !!opt_optional, - validator: /** @type {function(!fireauth.Auth) : boolean} */ ( - function(auth) { - return !!(auth && auth instanceof fireauth.Auth); - }) - }); -}; - - -/** - * Specifies an instance of Firebase User. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.firebaseUser = function(opt_optional) { - return /** @type {!fireauth.args.Argument} */ ({ - name: 'user', - typeLabel: 'an instance of Firebase User', - optional: !!opt_optional, - validator: /** @type {function(!fireauth.AuthUser) : boolean} */ ( - function(user) { - return !!(user && user instanceof fireauth.AuthUser); - }) - }); -}; - - -/** - * Specifies an instance of Firebase App. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.firebaseApp = function(opt_optional) { - return /** @type {!fireauth.args.Argument} */ ({ - name: 'app', - typeLabel: 'an instance of Firebase App', - optional: !!opt_optional, - validator: /** @type {function(!firebase.app.App) : boolean} */ ( - function(app) { - return !!(app && app instanceof firebase.app.App); - }) - }); -}; - - -/** - * Specifies an argument that implements the fireauth.AuthCredential interface. - * @param {?fireauth.idp.ProviderId=} opt_requiredProviderId The required type - * of provider. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.authCredential = - function(opt_requiredProviderId, opt_name, opt_optional) { - var name = opt_name || - (opt_requiredProviderId ? - opt_requiredProviderId + 'Credential' : - 'credential'); - var typeLabel = opt_requiredProviderId ? - 'a valid ' + opt_requiredProviderId + ' credential' : - 'a valid credential'; - return /** @type {!fireauth.args.Argument} */ ({ - name: name, - typeLabel: typeLabel, - optional: !!opt_optional, - validator: /** @type {function(!fireauth.AuthCredential) : boolean} */ ( - function(credential) { - if (!credential) { - return false; - } - // If opt_requiredProviderId is set, make sure it matches the - // credential's providerId. - var matchesRequiredProvider = !opt_requiredProviderId || - (credential['providerId'] === opt_requiredProviderId); - return !!(credential.getIdTokenProvider && matchesRequiredProvider); - }) - }); -}; - - -/** - * Specifies an argument that implements the fireauth.MultiFactorAssertion - * interface. - * @param {?string=} requiredFactorId The required type of second factor. - * @param {?string=} optionalName The name of the argument. - * @param {?boolean=} optionalArg Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.multiFactorAssertion = - function(requiredFactorId, optionalName, optionalArg) { - var name = optionalName || - (requiredFactorId ? - requiredFactorId + 'MultiFactorAssertion' : 'multiFactorAssertion'); - var typeLabel = requiredFactorId ? - 'a valid ' + requiredFactorId + ' multiFactorAssertion' : - 'a valid multiFactorAssertion'; - return /** @type {!fireauth.args.Argument} */ ({ - name: name, - typeLabel: typeLabel, - optional: !!optionalArg, - validator: - /** @type {function(!fireauth.MultiFactorAssertion) : boolean} */ ( - function(assertion) { - if (!assertion) { - return false; - } - // If requiredFactorId is set, make sure it matches the - // assertion's factorId. - var matchesRequiredFactor = !requiredFactorId || - (assertion['factorId'] === requiredFactorId); - return !!(assertion.process && matchesRequiredFactor); - }) - }); -}; - - -/** - * Specifies an argument that implements the fireauth.AuthProvider interface. - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.authProvider = function(opt_name, opt_optional) { - return /** @type {!fireauth.args.Argument} */ ({ - name: opt_name || 'authProvider', - typeLabel: 'a valid Auth provider', - optional: !!opt_optional, - validator: /** @type {function(!fireauth.AuthProvider) : boolean} */ ( - function(provider) { - return !!(provider && - provider['providerId'] && - provider.hasOwnProperty && - provider.hasOwnProperty('isOAuthProvider')); - }) - }); -}; - - -/** - * Specifies a phone info options argument. - * @param {?string=} name The name of the argument. - * @param {?boolean=} optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.phoneInfoOptions = function(name, optional) { - return /** @type {!fireauth.args.Argument} */ ({ - name: name || 'phoneInfoOptions', - typeLabel: 'valid phone info options', - optional: !!optional, - validator: /** @type {function(!Object) : boolean} */ ( - function(phoneInfoOptions) { - if (!phoneInfoOptions) { - return false; - } - // For multi-factor enrollment, phone number and MFA session should - // be provided. - if (phoneInfoOptions['session'] && - phoneInfoOptions['phoneNumber']) { - return fireauth.args.validateMultiFactorSession_( - phoneInfoOptions['session'], - fireauth.MultiFactorSession.Type.ENROLL) && - typeof phoneInfoOptions['phoneNumber'] === 'string'; - // For multi-factor sign-in, phone multi-factor hint and MFA session - // are provided. - } else if (phoneInfoOptions['session'] && - phoneInfoOptions['multiFactorHint']) { - return fireauth.args.validateMultiFactorSession_( - phoneInfoOptions['session'], - fireauth.MultiFactorSession.Type.SIGN_IN) && - fireauth.args.validateMultiFactorInfo_( - phoneInfoOptions['multiFactorHint']); - // For multi-factor sign-in, phone multi-factor UID and MFA session - // are provided. - } else if (phoneInfoOptions['session'] && - phoneInfoOptions['multiFactorUid']) { - return fireauth.args.validateMultiFactorSession_( - phoneInfoOptions['session'], - fireauth.MultiFactorSession.Type.SIGN_IN) && - typeof phoneInfoOptions['multiFactorUid'] === 'string'; - // For single-factor sign-in, only phone number needs to be provided. - } else if (phoneInfoOptions['phoneNumber']) { - return typeof phoneInfoOptions['phoneNumber'] === 'string'; - } - return false; - }) - }); -}; - - -/** - * @param {*} session The multi-factor session object. - * @param {!fireauth.MultiFactorSession.Type} type The session type. - * @return {boolean} Whether the seesion is a valid multi-factor session. - * @private - */ -fireauth.args.validateMultiFactorSession_ = function(session, type) { - return goog.isObject(session) && typeof session.type === 'string' && - session.type === type && - typeof session.getRawSession === 'function'; -}; - - -/** - * @param {*} info The multi-factor info object. - * @return {boolean} Whether the info is a valid multi-factor info. - * @private - */ -fireauth.args.validateMultiFactorInfo_ = function(info) { - return goog.isObject(info) && typeof info['uid'] === 'string'; -}; - - -/** - * Specifies an argument that implements the fireauth.MultiFactorInfo - * interface. - * @param {?string=} name The name of the argument. - * @param {?boolean=} optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.multiFactorInfo = function(name, optional) { - return /** @type {!fireauth.args.Argument} */ ({ - name: name || 'multiFactorInfo', - typeLabel: 'a valid multiFactorInfo', - optional: !!optional, - validator: fireauth.args.validateMultiFactorInfo_ - }); -}; - - -/** - * Specifies an argument that implements the firebase.auth.ApplicationVerifier - * interface. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.applicationVerifier = function(opt_optional) { - return /** @type {!fireauth.args.Argument} */ ({ - name: 'applicationVerifier', - typeLabel: 'an implementation of firebase.auth.ApplicationVerifier', - optional: !!opt_optional, - validator: - /** @type {function(!firebase.auth.ApplicationVerifier) : boolean} */ ( - function(applicationVerifier) { - return !!(applicationVerifier && - typeof applicationVerifier.type === 'string' && - typeof applicationVerifier.verify === 'function'); - }) - }); -}; - - -/** - * Specifies an argument that can be either of two argument types. - * @param {!fireauth.args.Argument} optionA - * @param {!fireauth.args.Argument} optionB - * @param {?string=} opt_name The name of the argument. - * @param {?boolean=} opt_optional Whether or not this argument is optional. - * Defaults to false. - * @return {!fireauth.args.Argument} - */ -fireauth.args.or = function(optionA, optionB, opt_name, opt_optional) { - return { - name: opt_name || '', - typeLabel: optionA.typeLabel + ' or ' + optionB.typeLabel, - optional: !!opt_optional, - validator: function(value) { - return optionA.validator(value) || optionB.validator(value); - } - }; -}; - - -/** - * @param {string} str - * @return {string} The string surrounded with quotes. - * @private - */ -fireauth.args.quoteString_ = function(str) { - return '"' + str + '"'; -}; diff --git a/packages/auth/src/auth.js b/packages/auth/src/auth.js deleted file mode 100644 index 2efcae9d8dd..00000000000 --- a/packages/auth/src/auth.js +++ /dev/null @@ -1,2258 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview The headless Auth class used for authenticating Firebase users. - */ - -goog.provide('fireauth.Auth'); - -goog.require('fireauth.ActionCodeInfo'); -goog.require('fireauth.ActionCodeSettings'); -goog.require('fireauth.AdditionalUserInfo'); -goog.require('fireauth.AuthCredential'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.AuthEventHandler'); -goog.require('fireauth.AuthEventManager'); -goog.require('fireauth.AuthProvider'); -goog.require('fireauth.AuthSettings'); -goog.require('fireauth.AuthUser'); -goog.require('fireauth.ConfirmationResult'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.MultiFactorError'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.UserEventType'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.deprecation'); -goog.require('fireauth.idp'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.object'); -goog.require('fireauth.storage.RedirectUserManager'); -goog.require('fireauth.storage.UserManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.array'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventTarget'); -goog.require('goog.object'); - - - -/** - * Creates the Firebase Auth corresponding for the App provided. - * - * @param {!firebase.app.App} app The corresponding Firebase App. - * @constructor - * @implements {fireauth.AuthEventHandler} - * @implements {firebase.Service} - * @extends {goog.events.EventTarget} - */ -fireauth.Auth = function(app) { - /** @private {boolean} Whether this instance is deleted. */ - this.deleted_ = false; - /** The Auth instance's settings object. */ - fireauth.object.setReadonlyProperty( - this, 'settings', new fireauth.AuthSettings()); - /** Auth's corresponding App. */ - fireauth.object.setReadonlyProperty(this, 'app', app); - // Initialize RPC handler. - // API key is required for web client RPC calls. - if (this.app_().options && this.app_().options['apiKey']) { - var clientFullVersion = firebase.SDK_VERSION ? - fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION) : - null; - this.rpcHandler_ = new fireauth.RpcHandler( - this.app_().options && this.app_().options['apiKey'], - // Get the client Auth endpoint used. - fireauth.constants.getEndpointConfig(fireauth.constants.clientEndpoint), - clientFullVersion); - } else { - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_API_KEY); - } - /** @private {!Array|!goog.Promise>} List of - * pending promises. */ - this.pendingPromises_ = []; - /** @private {!Array} Auth token listeners. */ - this.authListeners_ = []; - /** @private {!Array} User change listeners. */ - this.userChangeListeners_ = []; - /** - * @private {!firebase.Subscribe} The subscribe function to the Auth ID token - * change observer. This will trigger on ID token changes, including - * token refresh on the same user. - */ - this.onIdTokenChanged_ = firebase.INTERNAL.createSubscribe( - goog.bind(this.initIdTokenChangeObserver_, this)); - /** - * @private {?string|undefined} The UID of the user that last triggered the - * user state change listener. - */ - this.userStateChangeUid_ = undefined; - /** - * @private {!firebase.Subscribe} The subscribe function to the user state - * change observer. - */ - this.onUserStateChanged_ = firebase.INTERNAL.createSubscribe( - goog.bind(this.initUserStateObserver_, this)); - // Set currentUser to null. - this.setCurrentUser_(null); - /** - * @private {!fireauth.storage.UserManager} The Auth user storage - * manager instance. - */ - this.userStorageManager_ = - new fireauth.storage.UserManager(this.getStorageKey()); - /** - * @private {!fireauth.storage.RedirectUserManager} The redirect user - * storagemanager instance. - */ - this.redirectUserStorageManager_ = - new fireauth.storage.RedirectUserManager(this.getStorageKey()); - /** - * @private {!goog.Promise} Promise that resolves when initial - * state is loaded from storage. - */ - this.authStateLoaded_ = this.registerPendingPromise_(this.initAuthState_()); - /** - * @private {!goog.Promise} Promise that resolves when state and - * redirect result is ready, after which sign in and sign out operations - * are safe to execute. - */ - this.redirectStateIsReady_ = this.registerPendingPromise_( - this.initAuthRedirectState_()); - /** @private {boolean} Whether initial state has already been resolved. */ - this.isStateResolved_ = false; - /** - * @private {!function()} The syncAuthChanges function with context set to - * auth instance. - */ - this.getSyncAuthUserChanges_ = goog.bind(this.syncAuthUserChanges_, this); - /** @private {!function(!fireauth.AuthUser):!goog.Promise} The handler for - * user state changes. */ - this.userStateChangeListener_ = - goog.bind(this.handleUserStateChange_, this); - /** @private {!function(!Object)} The handler for user token changes. */ - this.userTokenChangeListener_ = - goog.bind(this.handleUserTokenChange_, this); - /** @private {!function(!Object)} The handler for user deletion. */ - this.userDeleteListener_ = goog.bind(this.handleUserDelete_, this); - /** @private {!function(!Object)} The handler for user invalidation. */ - this.userInvalidatedListener_ = goog.bind(this.handleUserInvalidated_, this); - /** - * @private {?fireauth.AuthEventManager} The Auth event manager instance. - */ - this.authEventManager_ = null; - // TODO: find better way to enable or disable auth event manager. - if (fireauth.AuthEventManager.ENABLED) { - // Initialize Auth event manager to handle popup and redirect operations. - this.initAuthEventManager_(); - } - - // Export INTERNAL namespace. - this.INTERNAL = {}; - this.INTERNAL['delete'] = goog.bind(this.delete, this); - this.INTERNAL['logFramework'] = goog.bind(this.logFramework, this); - /** - * @private {number} The number of Firebase services subscribed to Auth - * changes. - */ - this.firebaseServices_ = 0; - // Add call to superclass constructor. - fireauth.Auth.base(this, 'constructor'); - // Initialize readable/writable Auth properties. - this.initializeReadableWritableProps_(); - /** - * @private {!Array} List of Firebase frameworks/libraries used. This - * is currently only used to log FirebaseUI. - */ - this.frameworks_ = []; - - /** - * @private {?fireauth.constants.EmulatorSettings} The current - * emulator settings. - */ - this.emulatorConfig_ = null; -}; -goog.inherits(fireauth.Auth, goog.events.EventTarget); - - -/** - * Language code change custom event. - * @param {?string} languageCode The new language code. - * @constructor - * @extends {goog.events.Event} - */ -fireauth.Auth.LanguageCodeChangeEvent = function(languageCode) { - goog.events.Event.call( - this, fireauth.constants.AuthEventType.LANGUAGE_CODE_CHANGED); - this.languageCode = languageCode; -}; -goog.inherits(fireauth.Auth.LanguageCodeChangeEvent, goog.events.Event); - - -/** - * Emulator config change custom event. - * @param {?fireauth.constants.EmulatorSettings} emulatorConfig The new - * emulator settings. - * @constructor - * @extends {goog.events.Event} - */ -fireauth.Auth.EmulatorConfigChangeEvent = function(emulatorConfig) { - goog.events.Event.call(this, fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED); - this.emulatorConfig = emulatorConfig; -}; -goog.inherits(fireauth.Auth.EmulatorConfigChangeEvent, goog.events.Event); - - -/** - * Framework change custom event. - * @param {!Array} frameworks The new frameworks array. - * @constructor - * @extends {goog.events.Event} - */ -fireauth.Auth.FrameworkChangeEvent = function(frameworks) { - goog.events.Event.call( - this, fireauth.constants.AuthEventType.FRAMEWORK_CHANGED); - this.frameworks = frameworks; -}; -goog.inherits(fireauth.Auth.FrameworkChangeEvent, goog.events.Event); - - -/** - * Changes the Auth state persistence to the specified one. - * @param {!fireauth.authStorage.Persistence} persistence The Auth state - * persistence mechanism. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.setPersistence = function(persistence) { - // TODO: fix auth.delete() behavior and how this affects persistence - // change after deletion. - // Throw an error if already destroyed. - // Set current persistence. - var p = this.userStorageManager_.setPersistence(persistence); - return /** @type {!goog.Promise} */ (this.registerPendingPromise_(p)); -}; - - -/** - * Get rid of Closure warning - the property is adding in the constructor. - * @type {!firebase.app.App} - */ -fireauth.Auth.prototype.app; - - -/** - * Sets the language code. - * @param {?string} languageCode - */ -fireauth.Auth.prototype.setLanguageCode = function(languageCode) { - // Don't do anything if no change detected. - if (this.languageCode_ !== languageCode && !this.deleted_) { - this.languageCode_ = languageCode; - // Update custom Firebase locale field. - this.rpcHandler_.updateCustomLocaleHeader(this.languageCode_); - // Notify external language code change listeners. - this.notifyLanguageCodeListeners_(); - } -}; - - -/** - * Returns the current auth instance's language code if available. - * @return {?string} - */ -fireauth.Auth.prototype.getLanguageCode = function() { - return this.languageCode_; -}; - - -/** - * Sets the current language to the default device/browser preference. - */ -fireauth.Auth.prototype.useDeviceLanguage = function() { - this.setLanguageCode(fireauth.util.getUserLanguage()); -}; - - -/** - * Sets the emulator configuration (go/firebase-emulator-connection-api). - * @param {string} url The url for the Auth emulator. - * @param {?Object=} options Optional options to specify emulator settings. - */ -fireauth.Auth.prototype.useEmulator = function(url, options) { - // Emulator config can only be set once. - if (!this.emulatorConfig_) { - if (!/^https?:\/\//.test(url)) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'Emulator URL must start with a valid scheme (http:// or https://).'); - } - // Emit a warning so dev knows we are now in test mode. - const disableBanner = options ? !!options['disableWarnings'] : false; - this.emitEmulatorWarning_(disableBanner); - // Persist the config. - this.emulatorConfig_ = {url, disableWarnings: disableBanner}; - // Disable app verification. - this.settings_().setAppVerificationDisabledForTesting(true); - // Update RPC handler endpoints. - this.rpcHandler_.updateEmulatorConfig(this.emulatorConfig_); - // Notify external event listeners. - this.notifyEmulatorConfigListeners_(); - } -} - - -/** - * Emits a console info and a visual banner if emulator integration is - * enabled. - * @param {boolean} disableBanner Whether visual banner should be disabled. - * @private - */ -fireauth.Auth.prototype.emitEmulatorWarning_ = function(disableBanner) { - fireauth.util.consoleInfo('WARNING: You are using the Auth Emulator,' + - ' which is intended for local testing only. Do not use with' + - ' production credentials.'); - if (goog.global.document && !disableBanner) { - fireauth.util.onDomReady().then(() => { - const ele = goog.global.document.createElement('div'); - ele.innerText = 'Running in emulator mode. Do not use with production' + - ' credentials.'; - ele.style.position = 'fixed'; - ele.style.width = '100%'; - ele.style.backgroundColor = '#ffffff'; - ele.style.border = '.1em solid #000000'; - ele.style.color = '#b50000'; - ele.style.bottom = '0px'; - ele.style.left = '0px'; - ele.style.margin = '0px'; - ele.style.zIndex = 10000; - ele.style.textAlign = 'center'; - ele.classList.add('firebase-emulator-warning'); - goog.global.document.body.appendChild(ele); - }); - } -} - - -/** - * @return {?fireauth.constants.EmulatorConfig} - */ -fireauth.Auth.prototype.getEmulatorConfig = function() { - if (!this.emulatorConfig_) { - return null; - } - const uri = goog.Uri.parse(this.emulatorConfig_.url); - return /** @type {!fireauth.constants.EmulatorConfig} */ ( - fireauth.object.makeReadonlyCopy({ - 'protocol': uri.getScheme(), - 'host': uri.getDomain(), - 'port': uri.getPort(), - 'options': fireauth.object.makeReadonlyCopy({ - 'disableWarnings': this.emulatorConfig_.disableWarnings, - }), - })); -} - - -/** - * @param {string} frameworkId The framework identifier. - */ -fireauth.Auth.prototype.logFramework = function(frameworkId) { - // Theoretically multiple frameworks could be used - // (angularfire and FirebaseUI). Once a framework is used, it is not going - // to be unused, so no point adding a method to remove the framework ID. - this.frameworks_.push(frameworkId); - // Update the client version in RPC handler with the new frameworks. - this.rpcHandler_.updateClientVersion(firebase.SDK_VERSION ? - fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION, - this.frameworks_) : - null); - this.dispatchEvent(new fireauth.Auth.FrameworkChangeEvent( - this.frameworks_)); -}; - - -/** @return {!Array} The current Firebase frameworks. */ -fireauth.Auth.prototype.getFramework = function() { - return goog.array.clone(this.frameworks_); -}; - - -/** - * Updates the framework list on the provided user and configures the user to - * listen to the Auth instance for any framework ID changes. - * @param {!fireauth.AuthUser} user The user to whose framework list needs to be - * updated. - * @private - */ -fireauth.Auth.prototype.setUserFramework_ = function(user) { - // Sets the framework ID on the user. - user.setFramework(this.frameworks_); - // Sets current Auth instance as framework list change dispatcher on the user. - user.setFrameworkChangeDispatcher(this); -}; - - -/** - * Sets the tenant ID. - * @param {?string} tenantId The tenant ID of the tenant project if available. - */ -fireauth.Auth.prototype.setTenantId = function(tenantId) { - // Don't do anything if no change detected. - if (this.tenantId_ !== tenantId && !this.deleted_) { - this.tenantId_ = tenantId; - this.rpcHandler_.updateTenantId(this.tenantId_); - } -}; - - -/** - * Returns the current Auth instance's tenant ID. - * @return {?string} - */ -fireauth.Auth.prototype.getTenantId = function() { - return this.tenantId_; -}; - - -/** - * Initializes readable/writable properties on Auth. - * @suppress {invalidCasts} - * @private - */ -fireauth.Auth.prototype.initializeReadableWritableProps_ = function() { - Object.defineProperty(/** @type {!Object} */ (this), 'lc', { - /** - * @this {!Object} - * @return {?string} The current language code. - */ - get: function() { - return this.getLanguageCode(); - }, - /** - * @this {!Object} - * @param {string} value The new language code. - */ - set: function(value) { - this.setLanguageCode(value); - }, - enumerable: false - }); - // Initialize to null. - /** @private {?string} The current Auth instance's language code. */ - this.languageCode_ = null; - - // Initialize tenant ID. - Object.defineProperty(/** @type {!Object} */ (this), 'ti', { - /** - * @this {!Object} - * @return {?string} The current tenant ID. - */ - get: function() { - return this.getTenantId(); - }, - /** - * @this {!Object} - * @param {?string} value The new tenant ID. - */ - set: function(value) { - this.setTenantId(value); - }, - enumerable: false - }); - // Initialize to null. - /** @private {?string} The current Auth instance's tenant ID. */ - this.tenantId_ = null; - - // Add the emulator configuration property (readonly). - Object.defineProperty(/** @type {!Object} */ (this), 'emulatorConfig', { - /** - * @this {!Object} - * @return {?fireauth.constants.EmulatorConfig} The emulator config if - * enabled. - */ - get: function() { - return this.getEmulatorConfig(); - }, - enumerable: false - }); -}; - - -/** - * Notifies all external listeners of the language code change. - * @private - */ -fireauth.Auth.prototype.notifyLanguageCodeListeners_ = function() { - // Notify external listeners on the language code change. - this.dispatchEvent(new fireauth.Auth.LanguageCodeChangeEvent( - this.getLanguageCode())); -}; - - -/** - * Notifies all external listeners of the emulator config change. - * @private - */ -fireauth.Auth.prototype.notifyEmulatorConfigListeners_ = function() { - // Notify external listeners on the emulator config change. - this.dispatchEvent( - new fireauth.Auth.EmulatorConfigChangeEvent(this.emulatorConfig_)); -} - - -/** - * @return {!Object} The object representation of the Auth instance. - * @override - */ -fireauth.Auth.prototype.toJSON = function() { - // Return the plain object representation in case JSON.stringify is called on - // an Auth instance. - return { - 'apiKey': this.app_().options['apiKey'], - 'authDomain': this.app_().options['authDomain'], - 'appName': this.app_().name, - 'currentUser': this.currentUser_() && this.currentUser_().toPlainObject() - }; -}; - - -/** - * Returns the Auth event manager promise. - * @return {!goog.Promise} - * @private - */ -fireauth.Auth.prototype.getAuthEventManager_ = function() { - // Either return cached Auth event manager promise provider if available or a - // promise that rejects with missing Auth domain error. - return this.eventManagerProviderPromise_ || - goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_AUTH_DOMAIN)); -}; - - -/** - * Initializes the Auth event manager when state is ready. - * @private - */ -fireauth.Auth.prototype.initAuthEventManager_ = function() { - // Initialize Auth event manager on initState. - var self = this; - var authDomain = this.app_().options['authDomain']; - var apiKey = this.app_().options['apiKey']; - // Make sure environment also supports popup and redirect. - if (authDomain && fireauth.util.isPopupRedirectSupported()) { - // Auth domain is required for Auth event manager to resolve. - // Auth state has to be loaded first. One reason is to process link events. - this.eventManagerProviderPromise_ = this.authStateLoaded_.then(function() { - if (self.deleted_) { - return; - } - // By this time currentUser should be ready if available and will be able - // to resolve linkWithRedirect if detected. - self.authEventManager_ = fireauth.AuthEventManager.getManager( - authDomain, - apiKey, - self.app_().name, - self.emulatorConfig_); - // Subscribe Auth instance. - self.authEventManager_.subscribe(self); - // Subscribe current user by enabling popup and redirect on that user. - if (self.currentUser_()) { - self.currentUser_().enablePopupRedirect(); - } - // If a redirect user is present, subscribe to popup and redirect events. - // In case current user was not available and the developer called link - // with redirect on a signed out user, this will work and the linked - // logged out user will be returned in getRedirectResult. - // current user and redirect user are the same (was already logged in), - // currentUser will have priority as it is subscribed before redirect - // user. This change will also allow further popup and redirect events on - // the redirect user going forward. - if (self.redirectUser_) { - self.redirectUser_.enablePopupRedirect(); - // Set the user language for the redirect user. - self.setUserLanguage_( - /** @type {!fireauth.AuthUser} */ (self.redirectUser_)); - // Set the user Firebase frameworks for the redirect user. - self.setUserFramework_( - /** @type {!fireauth.AuthUser} */(self.redirectUser_)); - // Set the user Emulator configuration for the redirect user. - self.setUserEmulatorConfig_( - /** @type {!fireauth.AuthUser} */(self.redirectUser_)); - // Reference to redirect user no longer needed. - self.redirectUser_ = null; - } - return self.authEventManager_; - }); - } -}; - - -/** - * @param {!fireauth.AuthEvent.Type} mode The Auth type mode. - * @param {?string=} opt_eventId The event ID. - * @return {boolean} Whether the auth event handler can handler the provided - * event. - * @override - */ -fireauth.Auth.prototype.canHandleAuthEvent = function(mode, opt_eventId) { - // Only sign in events are handled. - switch (mode) { - // Accept all general sign in with redirect and unknowns. - // Migrating redirect events to use session storage will prevent this event - // from leaking to other tabs. - case fireauth.AuthEvent.Type.UNKNOWN: - case fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT: - return true; - case fireauth.AuthEvent.Type. SIGN_IN_VIA_POPUP: - // Pending sign in with popup event must match the stored popup event ID. - return this.popupEventId_ == opt_eventId && - !!this.pendingPopupResolvePromise_; - default: - return false; - } -}; - - -/** - * Completes the pending popup operation. If error is not null, rejects with the - * error. Otherwise, it resolves with the popup redirect result. - * @param {!fireauth.AuthEvent.Type} mode The Auth type mode. - * @param {?fireauth.AuthEventManager.Result} popupRedirectResult The result - * to resolve with when no error supplied. - * @param {?fireauth.AuthError} error When supplied, the promise will reject. - * @param {?string=} opt_eventId The event ID. - * @override - */ -fireauth.Auth.prototype.resolvePendingPopupEvent = - function(mode, popupRedirectResult, error, opt_eventId) { - // Only handles popup events of type sign in and which match popup event ID. - if (mode != fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP || - this.popupEventId_ != opt_eventId) { - return; - } - if (error && this.pendingPopupRejectPromise_) { - // Reject with error for supplied mode. - this.pendingPopupRejectPromise_(error); - } else if (popupRedirectResult && - !error && - this.pendingPopupResolvePromise_) { - // Resolve with result for supplied mode. - this.pendingPopupResolvePromise_(popupRedirectResult); - } - // Now that event is resolved, delete popup timeout promise. - if (this.popupTimeoutPromise_) { - this.popupTimeoutPromise_.cancel(); - this.popupTimeoutPromise_ = null; - } - // Delete pending promises. - delete this.pendingPopupResolvePromise_; - delete this.pendingPopupRejectPromise_; -}; - - -/** - * Returns the handler's appropriate popup and redirect sign in operation - * finisher. - * @param {!fireauth.AuthEvent.Type} mode The Auth type mode. - * @param {?string=} opt_eventId The optional event ID. - * @return {?function(string, string, ?string, - * ?string=):!goog.Promise} - * @override - */ -fireauth.Auth.prototype.getAuthEventHandlerFinisher = - function(mode, opt_eventId) { - // Sign in events will be completed by finishPopupAndRedirectSignIn. - if (mode == fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT) { - return goog.bind(this.finishPopupAndRedirectSignIn, this); - } else if (mode == fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP && - this.popupEventId_ == opt_eventId && - this.pendingPopupResolvePromise_) { - return goog.bind(this.finishPopupAndRedirectSignIn, this); - } - return null; -}; - - -/** - * Finishes the popup and redirect sign in operations. - * @param {string} requestUri The callback url with the oauth response. - * @param {string} sessionId The session id used to generate the authUri. - * @param {?string} tenantId The tenant ID. - * @param {?string=} opt_postBody The optional POST body content. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.finishPopupAndRedirectSignIn = - function(requestUri, sessionId, tenantId, opt_postBody) { - var self = this; - // Verify assertion request. - var request = { - 'requestUri': requestUri, - 'postBody': opt_postBody, - 'sessionId': sessionId, - // Even if tenant ID is null, still pass it to RPC handler explicitly so - // that it won't be overridden by RPC handler's tenant ID. - 'tenantId': tenantId - }; - // Now that popup has responded, delete popup timeout promise. - if (this.popupTimeoutPromise_) { - this.popupTimeoutPromise_.cancel(); - this.popupTimeoutPromise_ = null; - } - // When state is ready, run verify assertion request. - // This will only run either after initial and redirect state is ready for - // popups or after initial state is ready for redirect resolution. - return self.authStateLoaded_.then(function() { - return self.signInWithIdTokenProvider_( - self.rpcHandler_.verifyAssertion(request)); - }); -}; - - -/** - * @return {string} The generated event ID used to identify a popup event. - * @private - */ -fireauth.Auth.prototype.generateEventId_ = function() { - return fireauth.util.generateEventId(); -}; - - -/** - * Signs in to Auth provider via popup. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInWithPopup = function(provider) { - // Check if popup and redirect are supported in this environment. - if (!fireauth.util.isPopupRedirectSupported()) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - } - var mode = fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP; - var self = this; - // Popup the window immediately to make sure the browser associates the - // popup with the click that triggered it. - - // Get provider settings. - var settings = fireauth.idp.getIdpSettings(provider['providerId']); - // There could multiple sign in with popup events in different windows. - // We need to match the correct popup to the correct pending promise. - var eventId = this.generateEventId_(); - // If incapable of redirecting popup from opener, popup destination URL - // directly. This could also happen in a sandboxed iframe. - var oauthHelperWidgetUrl = null; - if ((!fireauth.util.runsInBackground() || fireauth.util.isIframe()) && - this.app_().options['authDomain'] && - provider['isOAuthProvider']) { - oauthHelperWidgetUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - this.app_().options['authDomain'], - this.app_().options['apiKey'], - this.app_().name, - mode, - provider, - null, - eventId, - firebase.SDK_VERSION || null, - null, - null, - this.getTenantId(), - this.emulatorConfig_); - } - // The popup must have a name, otherwise when successive popups are triggered - // they will all render in the same instance and none will succeed since the - // popup cancel of first window will close the shared popup window instance. - var popupWin = - fireauth.util.popup( - oauthHelperWidgetUrl, - fireauth.util.generateRandomString(), - settings && settings.popupWidth, - settings && settings.popupHeight); - // Auth event manager must be available for popup sign in to be possible. - var p = this.getAuthEventManager_().then(function(manager) { - // Process popup request tagging it with newly created event ID. - return manager.processPopup( - popupWin, mode, provider, eventId, !!oauthHelperWidgetUrl, - self.getTenantId()); - }).then(function() { - return new goog.Promise(function(resolve, reject) { - // Expire other pending promises if still available.. - self.resolvePendingPopupEvent( - mode, - null, - new fireauth.AuthError(fireauth.authenum.Error.EXPIRED_POPUP_REQUEST), - // Existing pending popup event ID. - self.popupEventId_); - // Save current pending promises. - self.pendingPopupResolvePromise_ = resolve; - self.pendingPopupRejectPromise_ = reject; - // Overwrite popup event ID with new one corresponding to popup. - self.popupEventId_ = eventId; - // Keep track of timeout promise to cancel it on promise resolution before - // it times out. - self.popupTimeoutPromise_ = - self.authEventManager_.startPopupTimeout( - self, mode, /** @type {!Window} */ (popupWin), eventId); - }); - }).then(function(result) { - // On resolution, close popup if still opened and pass result through. - if (popupWin) { - fireauth.util.closeWindow(popupWin); - } - if (result) { - return fireauth.object.makeReadonlyCopy(result); - } - return null; - }).thenCatch(function(error) { - if (popupWin) { - fireauth.util.closeWindow(popupWin); - } - throw error; - }); - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_(p)); -}; - - -/** - * Signs in to Auth provider via redirect. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInWithRedirect = function(provider) { - // Check if popup and redirect are supported in this environment. - if (!fireauth.util.isPopupRedirectSupported()) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - } - var self = this; - var mode = fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT; - // Auth event manager must be available for sign in via redirect to be - // possible. - var p = this.getAuthEventManager_().then(function(manager) { - // Remember current persistence to apply it on the next page. - // This is the only time the state is passed to the next page (when user is - // not already logged in). - // This is not needed for link and reauthenticate as the user is already - // stored with specified persistence. - return self.userStorageManager_.savePersistenceForRedirect(); - }).then(function() { - // Process redirect operation. - return self.authEventManager_.processRedirect( - mode, provider, undefined, self.getTenantId()); - }); - return /** @type {!goog.Promise} */ (this.registerPendingPromise_(p)); -}; - - -/** - * Returns the redirect result. If coming back from a successful redirect sign - * in, will resolve to the signed in user. If coming back from an unsuccessful - * redirect sign, will reject with the proper error. If no redirect operation - * called, resolves with null. - * @return {!goog.Promise} - * @private - */ -fireauth.Auth.prototype.getRedirectResultWithoutClearing_ = function() { - // Check if popup and redirect are supported in this environment. - if (!fireauth.util.isPopupRedirectSupported()) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - } - var self = this; - // Auth event manager must be available for get redirect result to be - // possible. - var p = this.getAuthEventManager_().then(function(manager) { - // Return redirect result when resolved. - return self.authEventManager_.getRedirectResult(); - }).then(function(result) { - if (result) { - return fireauth.object.makeReadonlyCopy(result); - } - return null; - }); - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_(p)); -}; - - -/** - * In addition to returning the redirect result as in - * `getRedirectResultWithoutClearing_`, this will also clear the cached - * redirect result for security reasons. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.getRedirectResult = function() { - return this.getRedirectResultWithoutClearing_() - .then((result) => { - if (this.authEventManager_) { - this.authEventManager_.clearRedirectResult(); - } - return result; - }) - .thenCatch((error) => { - if (this.authEventManager_) { - this.authEventManager_.clearRedirectResult(); - } - throw error; - }); -}; - - -/** - * Asynchronously sets the provided user as currentUser on the current Auth - * instance. - * @param {?fireauth.AuthUser} user The user to be copied to Auth instance. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.updateCurrentUser = function(user) { - if (!user) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.NULL_USER)); - } - if (this.tenantId_ != user['tenantId']) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.TENANT_ID_MISMATCH)); - } - var self = this; - var options = {}; - options['apiKey'] = this.app_().options['apiKey']; - options['authDomain'] = this.app_().options['authDomain']; - options['appName'] = this.app_().name; - var newUser = fireauth.AuthUser.copyUser(user, options, - self.redirectUserStorageManager_, self.getFramework()); - return this.registerPendingPromise_( - this.redirectStateIsReady_.then(function() { - if (self.app_().options['apiKey'] != user.getApiKey()) { - // Throws auth/invalid-user-token if user doesn't belong to app. - // Throws auth/user-token-expired if token expires. - return newUser.reload(); - } - }).then(function() { - if (self.currentUser_() && user['uid'] == self.currentUser_()['uid']) { - // Same user signed in. Update user data and notify Auth listeners. - // No need to resubscribe to user events. - // TODO: Check if the user to copy is older than current user and skip - // the copy logic in that case. - self.currentUser_().copy(user); - return self.handleUserStateChange_(user); - } - self.setCurrentUser_(newUser); - // Enable popup and redirect events. - newUser.enablePopupRedirect(); - // Save user changes. - return self.handleUserStateChange_(newUser); - }).then(function(user) { - self.notifyAuthListeners_(); - })); -}; - - -/** - * Completes the headless sign in with the server response containing the STS - * access and refresh tokens, and sets the Auth user as current user while - * setting all listeners to it and saving it to storage. - * @param {!Object} idTokenResponse The ID token response from - * the server. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInWithIdTokenResponse = - function(idTokenResponse) { - var self = this; - var options = {}; - options['apiKey'] = self.app_().options['apiKey']; - options['authDomain'] = self.app_().options['authDomain']; - options['appName'] = self.app_().name; - if (self.emulatorConfig_) { - options['emulatorConfig'] = self.emulatorConfig_; - } - // Wait for state to be ready. - // This is used internally and is also used for redirect sign in so there is - // no need for waiting for redirect result to resolve since redirect result - // depends on it. - return this.authStateLoaded_.then(function() { - // Initialize an Auth user using the provided ID token response. - return fireauth.AuthUser.initializeFromIdTokenResponse( - options, - idTokenResponse, - /** @type {!fireauth.storage.RedirectUserManager} */ ( - self.redirectUserStorageManager_), - // Pass frameworks so they are logged in getAccountInfo while populating - // the user info. - self.getFramework()); - }).then(function(user) { - // Check if the same user is already signed in. - if (self.currentUser_() && - user['uid'] == self.currentUser_()['uid']) { - // Same user signed in. Update user data and notify Auth listeners. - // No need to resubscribe to user events. - self.currentUser_().copy(user); - return self.handleUserStateChange_(user); - } - // New user. - // Set current user and attach all listeners to it. - self.setCurrentUser_(user); - // Enable popup and redirect events. - user.enablePopupRedirect(); - // Save user changes. - return self.handleUserStateChange_(user); - }).then(function() { - // Notify external Auth listeners only when state is ready. - self.notifyAuthListeners_(); - }); -}; - - -/** - * Updates the current auth user and attaches event listeners to changes on it. - * Also removes all event listeners from previously signed in user. - * @param {?fireauth.AuthUser} user The current user instance. - * @private - */ -fireauth.Auth.prototype.setCurrentUser_ = function(user) { - // Must be called first before updating currentUser reference. - this.attachEventListeners_(user); - // Update currentUser property. - fireauth.object.setReadonlyProperty(this, 'currentUser', user); - if (user) { - // If a user is available, set the language code on it and set current Auth - // instance as language code change dispatcher. - this.setUserLanguage_(user); - // Set the current frameworks used on the user and set current Auth instance - // as the framework change dispatcher. - this.setUserFramework_(user); - // If a user is available, set the emulator config on it and set current - // Auth instance as emulator config change dispatcher. - this.setUserEmulatorConfig_(user); - } -}; - - -/** - * Signs out the current user while deleting the Auth user from storage and - * removing all listeners from it. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signOut = function() { - var self = this; - // Wait for final state to be ready first, otherwise a signed out user could - // come back to life. - var p = this.redirectStateIsReady_.then(function() { - // Clear any cached redirect result on sign out, even if user is already - // signed out. For example, sign in could fail due to account conflict - // error, the error in redirect result should still be cleared. There is - // also the use case where you keep a reference to a signed out user and - // call signedOutUser.linkWithRedirect(provider). Even though the user is - // signed out, getRedirectResult() will resolve with the modified signed - // out user. This could also throw an error - // (provider already linked, etc). - if (self.authEventManager_) { - self.authEventManager_.clearRedirectResult(); - } - // Ignore if already signed out. - if (!self.currentUser_()) { - return goog.Promise.resolve(); - } - // Detach all event listeners. - // Set current user to null. - self.setCurrentUser_(null); - // Remove current user from storage - return /** @type {!fireauth.storage.UserManager} */ ( - self.userStorageManager_).removeCurrentUser() - .then(function() { - // Notify external Auth listeners of this Auth change event. - self.notifyAuthListeners_(); - }); - }); - return /** @type {!goog.Promise} */ (this.registerPendingPromise_(p)); -}; - - -/** - * @return {!goog.Promise} A promise that resolved when any stored redirect user - * is loaded and removed from session storage and then stored locally. - * @private - */ -fireauth.Auth.prototype.initRedirectUser_ = function() { - var self = this; - var authDomain = this.app_().options['authDomain']; - // Get any saved redirect user and delete from session storage. - // Override user's authDomain with app's authDomain if there is a mismatch. - var p = /** @type {!fireauth.storage.RedirectUserManager} */ ( - this.redirectUserStorageManager_).getRedirectUser(authDomain) - .then(function(user) { - // Save redirect user. - self.redirectUser_ = user; - if (user) { - // Set redirect storage manager on user. - user.setRedirectStorageManager( - /** @type {!fireauth.storage.RedirectUserManager} */ ( - self.redirectUserStorageManager_)); - } - // Delete redirect user. - return /** @type {!fireauth.storage.RedirectUserManager} */ ( - self.redirectUserStorageManager_).removeRedirectUser(); - }); - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_(p)); -}; - - -/** - * Loads the initial Auth state for current application from web storage and - * initializes Auth user accordingly to reflect that state. This routine does - * not wait for any pending redirect result to be resolved. - * @return {!goog.Promise} Promise that resolves when state is ready, - * loaded from storage. - * @private - */ -fireauth.Auth.prototype.initAuthState_ = function() { - // Load current user from storage. - var self = this; - var authDomain = this.app_().options['authDomain']; - // Get any saved redirected user first. - var p = this.initRedirectUser_().then(function() { - // Override user's authDomain with app's authDomain if there is a mismatch. - return /** @type {!fireauth.storage.UserManager} */ ( - self.userStorageManager_).getCurrentUser(authDomain, self.emulatorConfig_); - }).then(function(user) { - // Logged in user. - if (user) { - // Set redirect storage manager on user. - user.setRedirectStorageManager( - /** @type {!fireauth.storage.RedirectUserManager} */ ( - self.redirectUserStorageManager_)); - // If the current user is undergoing a redirect operation, do not reload - // as that could could potentially delete the user if the token is - // expired. Instead any token problems will be detected via the - // verifyAssertion flow or the remaining flow. This is critical for - // reauthenticateWithRedirect as this flow is potentially used to recover - // from a token expiration error. - if (self.redirectUser_ && - self.redirectUser_.getRedirectEventId() == - user.getRedirectEventId()) { - return user; - } - // Confirm user valid first before setting listeners. - return user.reload().then(function() { - // Force user saving after reload as state change listeners not - // subscribed yet below via setCurrentUser_. Changes may have happened - // externally such as email actions or changes on another device. - return self.userStorageManager_.setCurrentUser(user).then(function() { - return user; - }); - }).thenCatch(function(error) { - if (error['code'] == 'auth/network-request-failed') { - // Do not delete the user from storage if connection is lost or app is - // offline. - return user; - } - // Invalid user, could be deleted, remove from storage and resolve with - // null. - return /** @type {!fireauth.storage.UserManager} */( - self.userStorageManager_).removeCurrentUser(); - }); - } - // No logged in user, resolve with null; - return null; - }).then(function(user) { - // Even though state not ready yet pending any redirect result. - // Current user needs to be available for link with redirect to complete. - // This will also set listener on the user changes in case state changes - // occur they would get updated in storage too. - self.setCurrentUser_(user || null); - }); - // In case the app is deleted before it is initialized with state from - // storage. - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_(p)); -}; - - -/** - * After initial Auth state is loaded, waits for any pending redirect result, - * resolves it and then adds the external Auth state change listeners and - * triggers first state of all observers. - * @return {!goog.Promise} Promise that resolves when state is ready - * taking into account any pending redirect result. - * @private - */ -fireauth.Auth.prototype.initAuthRedirectState_ = function() { - var self = this; - // Wait first for state to be loaded from storage. - return this.authStateLoaded_.then(function() { - // Resolve any pending redirect result. - return self.getRedirectResultWithoutClearing_(); - }).thenCatch(function(error) { - // Ignore any error in the process. Redirect could be not supported. - return; - }).then(function() { - // Make sure instance was not deleted before proceeding. - if (self.deleted_) { - return; - } - // Between init Auth state and get redirect result resolution there - // could have been a sign in attempt in another window. - // Force sync and then add listener to run sync on change below. - return self.getSyncAuthUserChanges_(); - }).thenCatch(function(error) { - // Ignore any error in the process. - return; - }).then(function() { - // Now that final state is ready, make sure instance was not deleted before - // proceeding. - if (self.deleted_) { - return; - } - // Initial state has been resolved. - self.isStateResolved_ = true; - // Add user state change listener so changes are synchronized with - // other windows and tabs. - /** @type {!fireauth.storage.UserManager} */ (self.userStorageManager_ - ).addCurrentUserChangeListener(self.getSyncAuthUserChanges_); - }); -}; - - -/** - * Synchronizes current Auth to stored auth state, used when external state - * changes occur. - * @return {!goog.Promise} - * @private - */ -fireauth.Auth.prototype.syncAuthUserChanges_ = function() { - // Get Auth user state from storage and compare to current state. - // Safe to run when no external change is detected. - var self = this; - var authDomain = this.app_().options['authDomain']; - // Override user's authDomain with app's authDomain if there is a mismatch. - return /** @type {!fireauth.storage.UserManager} */ ( - this.userStorageManager_).getCurrentUser(authDomain) - .then(function(user) { - // In case this was deleted. - if (self.deleted_) { - return; - } - // Since the authDomain could be modified here, saving to storage here - // could trigger an infinite loop of changes between this tab and - // another tab using different Auth domain but since sync Auth user - // changes does not save changes to storage, except for getToken below - // if the token needs refreshing but will stop triggering the first time - // the token is refreshed on one of the first tab that refreshes it. - // The latter should not happen anyway since getToken should be valid - // at all times since anything that triggers the storage change should - // have communicated with the backend and that requires a valid token. - // In addition, authDomain difference is an edge case to begin with. - - // If the same user is to be synchronized. - if (self.currentUser_() && - user && - self.currentUser_().hasSameUserIdAs(user)) { - // Data update, simply copy data changes. - self.currentUser_().copy(user); - // If tokens changed from previous user tokens, this will trigger - // notifyAuthListeners_. - return self.currentUser_().getIdToken(); - } else if (!self.currentUser_() && !user) { - // No change, do nothing (was signed out and remained signed out). - return; - } else { - // Update current Auth state. Either a new login or logout. - self.setCurrentUser_(user); - // If a new user is signed in, enabled popup and redirect on that - // user. - if (user) { - user.enablePopupRedirect(); - // Set redirect storage manager on user. - user.setRedirectStorageManager( - /** @type {!fireauth.storage.RedirectUserManager} */ ( - self.redirectUserStorageManager_)); - } - if (self.authEventManager_) { - self.authEventManager_.subscribe(self); - } - // Notify external Auth changes of Auth change event. - self.notifyAuthListeners_(); - } - }); -}; - - -/** - * Updates the language code on the provided user and configures the user to - * listen to the Auth instance for any language code changes. - * @param {!fireauth.AuthUser} user The user to whose language needs to be set. - * @private - */ -fireauth.Auth.prototype.setUserLanguage_ = function(user) { - // Sets the current language code on the user. - user.setLanguageCode(this.getLanguageCode()); - // Sets current Auth instance as language code change dispatcher on the user. - user.setLanguageCodeChangeDispatcher(this); -}; - - -/** - * Updates the emulator config on the provided user and configures the user - * to listen to the Auth instance for any emulator config changes. - * @param {!fireauth.AuthUser} user The user to whose emulator config needs - * to be set. - * @private - */ -fireauth.Auth.prototype.setUserEmulatorConfig_ = function(user) { - // Sets the current emulator config on the user. - user.setEmulatorConfig(this.emulatorConfig_); - // Sets current Auth instance as emulator config change dispatcher on the - // user. - user.setEmulatorConfigChangeDispatcher(this); -} - - -/** - * Handles user state changes. - * @param {!fireauth.AuthUser} user The user which triggered the state changes. - * @return {!goog.Promise} The promise that resolves when state changes are - * handled. - * @private - */ -fireauth.Auth.prototype.handleUserStateChange_ = function(user) { - // Save Auth user state changes. - return /** @type {!fireauth.storage.UserManager} */ ( - this.userStorageManager_).setCurrentUser(user); -}; - - -/** - * Handles user token changes. - * @param {!Object} event The token change event. - * @private - */ -fireauth.Auth.prototype.handleUserTokenChange_ = function(event) { - // This is only called when user is ready and Auth state has been resolved. - // Notify external Auth change listeners. - this.notifyAuthListeners_(); - // Save user token changes. - this.handleUserStateChange_(/** @type {!fireauth.AuthUser} */ ( - this.currentUser_())); -}; - - -/** - * Handles user deletion events. - * @param {!Object} event The user delete event. - * @private - */ -fireauth.Auth.prototype.handleUserDelete_ = function(event) { - // A deleted user will be treated like a sign out event. - this.signOut(); -}; - - -/** - * Handles user invalidation events. - * @param {!Object} event The user invalidation event. - * @private - */ -fireauth.Auth.prototype.handleUserInvalidated_ = function(event) { - // An invalidated user will be treated like a sign out event. - this.signOut(); -}; - - -/** - * Detaches all previous listeners on current user and reattach new listeners to - * provided user if not null. - * @param {?fireauth.AuthUser} user The user to attach event listeners to. - * @private - */ -fireauth.Auth.prototype.attachEventListeners_ = function(user) { - // Remove existing event listeners from previous current user if available. - if (this.currentUser_()) { - this.currentUser_().removeStateChangeListener( - this.userStateChangeListener_); - goog.events.unlisten( - this.currentUser_(), - fireauth.UserEventType.TOKEN_CHANGED, - this.userTokenChangeListener_); - goog.events.unlisten( - this.currentUser_(), - fireauth.UserEventType.USER_DELETED, - this.userDeleteListener_); - goog.events.unlisten( - this.currentUser_(), - fireauth.UserEventType.USER_INVALIDATED, - this.userInvalidatedListener_); - // Stop proactive token refresh on the current user. - this.currentUser_().stopProactiveRefresh(); - } - // If a new user is provided, attach event listeners to state, token, user - // invalidation and delete events. - if (user) { - user.addStateChangeListener(this.userStateChangeListener_); - goog.events.listen( - user, - fireauth.UserEventType.TOKEN_CHANGED, - this.userTokenChangeListener_); - goog.events.listen( - user, - fireauth.UserEventType.USER_DELETED, - this.userDeleteListener_); - goog.events.listen( - user, - fireauth.UserEventType.USER_INVALIDATED, - this.userInvalidatedListener_); - // Start proactive token refresh on new user if there is at least one - // Firebase service subscribed to Auth changes. - if (this.firebaseServices_ > 0) { - user.startProactiveRefresh(); - } - } -}; - - -/** - * Signs in with ID token promise provider. - * @param {!goog.Promise} idTokenPromise - * The rpc handler method that returns a promise which resolves with an ID - * token. - * @return {!goog.Promise} - * @private - */ -fireauth.Auth.prototype.signInWithIdTokenProvider_ = function(idTokenPromise) { - var self = this; - var credential = null; - var additionalUserInfo = null; - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_( - idTokenPromise - .then(function(idTokenResponse) { - // Get credential if available in the response. - credential = fireauth.AuthProvider.getCredentialFromResponse( - idTokenResponse); - // Get additional IdP data if available in the response. - additionalUserInfo = fireauth.AdditionalUserInfo.fromPlainObject( - idTokenResponse); - // When custom token is exchanged for idToken, continue sign in with - // ID token and return firebase Auth user. - return self.signInWithIdTokenResponse(idTokenResponse); - }, function(error) { - // Catch the MFA_REQUIRED error rejected in ID token promise and - // repackage it into a multi-factor error with additional IdP data - // if available. - var multiFactorError = null; - if (error && error['code'] === 'auth/multi-factor-auth-required') { - multiFactorError = fireauth.MultiFactorError.fromPlainObject( - error.toPlainObject(), - self, - goog.bind(self.handleMultiFactorIdTokenResolver_, self)); - } - throw multiFactorError || error; - }) - .then(function() { - // Resolve promise with a readonly user credential object. - return fireauth.object.makeReadonlyCopy({ - // Return the current user reference. - 'user': self.currentUser_(), - // Return any credential passed from the backend. - 'credential': credential, - // Return any additional IdP data passed from the backend. - 'additionalUserInfo': additionalUserInfo, - // Sign in operation type. - 'operationType': fireauth.constants.OperationType.SIGN_IN - }); - }))); -}; - - -/** - * Completes multi-factor sign-in with ID token response and additional IdP data - * if available. - * @param {{idToken: string, refreshToken: string}} response The successful - * sign-in response containing the new ID tokens. - * @return {!goog.Promise} A Promise that - * resolves with the updated `UserCredential`. - * @private - */ -fireauth.Auth.prototype.handleMultiFactorIdTokenResolver_ = - function(response) { - var self = this; - // Wait for state to be ready and then finish sign-in. - return this.redirectStateIsReady_.then(function() { - return self.signInWithIdTokenProvider_(goog.Promise.resolve(response)); - }); -}; - - -/** - * Initializes the Auth state change observer returned by the - * firebase.INTERNAL.createSubscribe helper. - * @param {!firebase.Observer} observer The Auth state change observer. - * @private - */ -fireauth.Auth.prototype.initIdTokenChangeObserver_ = function(observer) { - var self = this; - // Adds a listener that will transmit the event everytime it's called. - this.addAuthTokenListener(function(accessToken) { - observer.next(self.currentUser_()); - }); -}; - - -/** - * Initializes the user state change observer returned by the - * firebase.INTERNAL.createSubscribe helper. - * @param {!firebase.Observer} observer The user state change observer. - * @private - */ -fireauth.Auth.prototype.initUserStateObserver_ = function(observer) { - var self = this; - // Adds a listener that will transmit the event everytime it's called. - this.addUserChangeListener_(function(accessToken) { - observer.next(self.currentUser_()); - }); -}; - - -/** - * Adds an observer for Auth state changes, we need to wrap the call as - * the args checking code needs a method defined on the prototype this way, - * not within the constructor, and we also have to implement the behavior - * that will trigger an observer right away if state is ready. - * @param {!firebase.Observer|function(?fireauth.AuthUser)} - * nextOrObserver An observer object or a function triggered on change. - * @param {function(!fireauth.AuthError)=} opt_error Optional A function - * triggered on Auth error. - * @param {function()=} opt_completed Optional A function triggered when the - * observer is removed. - * @return {!function()} The unsubscribe function for the observer. - */ -fireauth.Auth.prototype.onIdTokenChanged = function( - nextOrObserver, opt_error, opt_completed) { - var self = this; - // State already determined. Trigger immediately, otherwise initState will - // take care of notifying all pending listeners on initialization. - // In this case we do not trigger synchronously and trigger via a resolved - // promise as required by specs. - if (this.isStateResolved_) { - // The observer cannot be called synchronously. We're using the - // native Promise implementation as otherwise it creates weird behavior - // where the order of promises resolution would not be as expected. - // It is due to the fact fireauth and firebase.app use their own - // and different promises library and this leads to calls resolutions order - // being different from the promises registration order. - Promise.resolve().then(function() { - if (typeof nextOrObserver === 'function') { - nextOrObserver(self.currentUser_()); - } else if (typeof nextOrObserver['next'] === 'function') { - nextOrObserver['next'](self.currentUser_()); - } - }); - } - return this.onIdTokenChanged_( - /** @type {!firebase.Observer|function(*)|undefined} */ (nextOrObserver), - /** @type {function(!Error)|undefined} */ (opt_error), - opt_completed); -}; - - -/** - * Adds an observer for user state changes, we need to wrap the call as - * the args checking code needs a method defined on the prototype this way, - * not within the constructor, and we also have to implement the behavior - * that will trigger an observer right away if state is ready. - * @param {!firebase.Observer|function(?fireauth.AuthUser)} - * nextOrObserver An observer object or a function triggered on change. - * @param {function(!fireauth.AuthError)=} opt_error Optional A function - * triggered on Auth error. - * @param {function()=} opt_completed Optional A function triggered when the - * observer is removed. - * @return {!function()} The unsubscribe function for the observer. - */ -fireauth.Auth.prototype.onAuthStateChanged = function( - nextOrObserver, opt_error, opt_completed) { - var self = this; - // State already determined. Trigger immediately, otherwise initState will - // take care of notifying all pending listeners on initialization. - // In this case we do not trigger synchronously and trigger via a resolved - // promise as required by specs. - if (this.isStateResolved_) { - // The observer cannot be called synchronously. We're using the - // native Promise implementation as otherwise it creates weird behavior - // where the order of promises resolution would not be as expected. - // It is due to the fact fireauth and firebase.app use their own - // and different promises library and this leads to calls resolutions order - // being different from the promises registration order. - Promise.resolve().then(function() { - // This ensures that the first time notifyAuthListeners_ is triggered, - // it has the correct UID before triggering the user state change - // listeners. - self.userStateChangeUid_ = self.getUid(); - if (typeof nextOrObserver === 'function') { - nextOrObserver(self.currentUser_()); - } else if (typeof nextOrObserver['next'] === 'function') { - nextOrObserver['next'](self.currentUser_()); - } - }); - } - return this.onUserStateChanged_( - /** @type {!firebase.Observer|function(*)|undefined} */ (nextOrObserver), - /** @type {function(!Error)|undefined} */ (opt_error), - opt_completed); -}; - - -/** - * Returns an STS token. If the cached one is unexpired it is directly returned. - * Otherwise the existing ID token or refresh token is exchanged for a new one. - * If there is no user signed in, returns null. - * - * This method is called getIdTokenInternal as the symbol getIdToken is not - * obfuscated, which could lead to developers incorrectly calling - * firebase.auth().getIdToken(). - * - * @param {boolean=} opt_forceRefresh Whether to force refresh token exchange. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.getIdTokenInternal = function(opt_forceRefresh) { - var self = this; - // Wait for state to be ready. - var p = this.redirectStateIsReady_.then(function() { - // Call user's underlying getIdToken method. - if (self.currentUser_()) { - return self.currentUser_().getIdToken(opt_forceRefresh) - .then(function(stsAccessToken) { - // This is used internally by other services which expect the access - // token to be returned in an object. - return { - 'accessToken': stsAccessToken - }; - }); - } - // No logged in user, return null token. - return null; - }); - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_(p)); -}; - - -/** - * Signs in a user asynchronously using a custom token and returns any - * additional user info data or credentials returned form the backend. - * @param {string} token The custom token to sign in with. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInWithCustomToken = function(token) { - var self = this; - // Wait for the redirect state to be determined before proceeding. If critical - // errors like web storage unsupported are detected, fail before RPC, instead - // of after. - return this.redirectStateIsReady_.then(function() { - return self.signInWithIdTokenProvider_( - self.getRpcHandler().verifyCustomToken(token)); - }).then(function(result) { - var user = result['user']; - // Manually sets the isAnonymous flag to false as the GetAccountInfo - // response will look like an anonymous user (no credentials visible). - user.updateProperty('isAnonymous', false); - // Save isAnonymous flag changes to current user in storage. - self.handleUserStateChange_(user); - return result; - }); -}; - - -/** - * Sign in using an email and password and returns any additional user info - * data or credentials returned form the backend. - * @param {string} email The email to sign in with. - * @param {string} password The password to sign in with. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInWithEmailAndPassword = - function(email, password) { - var self = this; - // Wait for the redirect state to be determined before proceeding. If critical - // errors like web storage unsupported are detected, fail before RPC, instead - // of after. - return this.redirectStateIsReady_.then(function() { - return self.signInWithIdTokenProvider_( - self.getRpcHandler().verifyPassword(email, password)); - }); -}; - - -/** - * Creates a new email and password account and returns any additional user - * info data or credentials returned form the backend. - * @param {string} email The email to sign up with. - * @param {string} password The password to sign up with. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.createUserWithEmailAndPassword = - function(email, password) { - var self = this; - // Wait for the redirect state to be determined before proceeding. If critical - // errors like web storage unsupported are detected, fail before RPC, instead - // of after. - return this.redirectStateIsReady_.then(function() { - return self.signInWithIdTokenProvider_( - self.getRpcHandler().createAccount(email, password)); - }); -}; - - -/** - * Logs into Firebase with the given 3rd party credentials and returns any - * additional user info data or credentials returned form the backend. - * @param {!fireauth.AuthCredential} credential The Auth credential. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInWithCredential = function(credential) { - // Credential could be extended in the future, so leave it to credential to - // decide how to retrieve ID token. - var self = this; - // Wait for the redirect state to be determined before proceeding. If critical - // errors like web storage unsupported are detected, fail before RPC, instead - // of after. - return this.redirectStateIsReady_.then(function() { - // Return the full response object and not just the user. - return self.signInWithIdTokenProvider_( - credential.getIdTokenProvider(self.getRpcHandler())); - }); -}; - - -/** - * Logs into Firebase with the given 3rd party credentials and returns any - * additional user info data or credentials returned form the backend. It has - * been deprecated in favor of signInWithCredential. - * @param {!fireauth.AuthCredential} credential The Auth credential. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInAndRetrieveDataWithCredential = - function(credential) { - fireauth.deprecation.log( - fireauth.deprecation.Deprecations.SIGN_IN_WITH_CREDENTIAL); - return this.signInWithCredential(credential); -}; - - -/** - * Signs in a user anonymously and returns any additional user info data or - * credentials returned form the backend. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInAnonymously = function() { - var self = this; - // Wait for the redirect state to be determined before proceeding. If critical - // errors like web storage unsupported are detected, fail before RPC, instead - // of after. - return this.redirectStateIsReady_.then(function() { - var user = self.currentUser_(); - // If an anonymous user is already signed in, no need to sign him again. - if (user && user['isAnonymous']) { - var additionalUserInfo = fireauth.object.makeReadonlyCopy({ - 'providerId': null, - 'isNewUser': false - }); - return fireauth.object.makeReadonlyCopy({ - // Return the signed in user reference. - 'user': user, - // Do not return credential for anonymous user. - 'credential': null, - // Return any additional IdP data. - 'additionalUserInfo': additionalUserInfo, - // Sign in operation type. - 'operationType': fireauth.constants.OperationType.SIGN_IN - }); - } else { - // No anonymous user currently signed in. - return self.signInWithIdTokenProvider_( - self.getRpcHandler().signInAnonymously()) - .then(function(result) { - var user = result['user']; - // Manually sets the isAnonymous flag to true as - // initializeFromIdTokenResponse uses the default value of false and - // even though getAccountInfo sets that to true, it will be reverted - // to false in reloadWithoutSaving. - // TODO: consider optimizing this and cleaning these manual - // overwrites. - user.updateProperty('isAnonymous', true); - // Save isAnonymous flag changes to current user in storage. - self.handleUserStateChange_(user); - return result; - }); - } - }); -}; - - -/** - * @return {string} The key used for storing Auth state. - */ -fireauth.Auth.prototype.getStorageKey = function() { - return fireauth.util.createStorageKey( - this.app_().options['apiKey'], - this.app_().name); -}; - - -/** - * @return {!firebase.app.App} The Firebase App this auth object is connected - * to. - * @private - */ -fireauth.Auth.prototype.app_ = function() { - return this['app']; -}; - - -/** - * @return {!fireauth.AuthSettings} The settings for this Auth object. - * @private - */ -fireauth.Auth.prototype.settings_ = function () { - return this['settings']; -}; - - -/** - * @return {!fireauth.RpcHandler} The RPC handler. - */ -fireauth.Auth.prototype.getRpcHandler = function() { - return this.rpcHandler_; -}; - - -/** - * @return {?fireauth.AuthUser} The currently logged in user. - * @private - */ -fireauth.Auth.prototype.currentUser_ = function() { - return this['currentUser']; -}; - - -/** @return {?string} The current user UID if available, null if not. */ -fireauth.Auth.prototype.getUid = function() { - return (this.currentUser_() && this.currentUser_()['uid']) || null; -}; - - -/** - * @return {?string} The last cached access token. - * @private - */ -fireauth.Auth.prototype.getLastAccessToken_ = function() { - return (this.currentUser_() && this.currentUser_()['_lat']) || null; -}; - - - -/** - * Called internally on Auth state change to notify listeners. - * @private - */ -fireauth.Auth.prototype.notifyAuthListeners_ = function() { - // Only run when state is resolved. After state is resolved, the Auth listener - // will always trigger. - if (this.isStateResolved_) { - for (var i = 0; i < this.authListeners_.length; i++) { - if (this.authListeners_[i]) { - this.authListeners_[i](this.getLastAccessToken_()); - } - } - // Trigger user change only if UID changed. - if (this.userStateChangeUid_ !== this.getUid() && - this.userChangeListeners_.length) { - // Update user state change UID. - this.userStateChangeUid_ = this.getUid(); - // Trigger all subscribed user state change listeners. - for (var i = 0; i < this.userChangeListeners_.length; i++) { - if (this.userChangeListeners_[i]) { - this.userChangeListeners_[i](this.getLastAccessToken_()); - } - } - } - } -}; - - -/** - * Attaches a listener function to Auth state change. - * This is used only by internal Firebase services. - * @param {!function(?string)} listener The auth state change listener. - */ -fireauth.Auth.prototype.addAuthTokenListenerInternal = function(listener) { - this.addAuthTokenListener(listener); - // This is not exact science but should be good enough to detect Firebase - // services subscribing to Auth token changes. - // This is needed to start proactive token refresh on a user. - this.firebaseServices_++; - if (this.firebaseServices_ > 0 && this.currentUser_()) { - // Start proactive token refresh on the current user. - this.currentUser_().startProactiveRefresh(); - } -}; - - -/** - * Detaches the provided listener from Auth state change event. - * This is used only by internal Firebase services. - * @param {!function(?string)} listener The Auth state change listener. - */ -fireauth.Auth.prototype.removeAuthTokenListenerInternal = function(listener) { - // This is unlikely to be called by Firebase services. Services are unlikely - // to remove Auth token listeners. - // Make sure listener is still subscribed before decrementing. - var self = this; - goog.array.forEach(this.authListeners_, function(ele) { - // This covers the case where the same listener is subscribed more than - // once. - if (ele == listener) { - self.firebaseServices_--; - } - }); - if (this.firebaseServices_ < 0) { - this.firebaseServices_ = 0; - } - if (this.firebaseServices_ == 0 && this.currentUser_()) { - // Stop proactive token refresh on the current user. - this.currentUser_().stopProactiveRefresh(); - } - this.removeAuthTokenListener(listener); -}; - - -/** - * Attaches a listener function to Auth state change. - * @param {!function(?string)} listener The Auth state change listener. - */ -fireauth.Auth.prototype.addAuthTokenListener = function(listener) { - var self = this; - // Save listener. - this.authListeners_.push(listener); - // Make sure redirect state is ready and then trigger listener. - this.registerPendingPromise_(this.redirectStateIsReady_.then(function() { - // Do nothing if deleted. - if (self.deleted_) { - return; - } - // Make sure listener is still subscribed. - if (goog.array.contains(self.authListeners_, listener)) { - // Trigger the first call for this now that redirect state is resolved. - listener(self.getLastAccessToken_()); - } - })); -}; - - -/** - * Detaches the provided listener from Auth state change event. - * @param {!function(?string)} listener The Auth state change listener. - */ -fireauth.Auth.prototype.removeAuthTokenListener = function(listener) { - // Remove from Auth listeners. - goog.array.removeAllIf(this.authListeners_, function(ele) { - return ele == listener; - }); -}; - - -/** - * Attaches a listener function to user state change. - * @param {!function(?string)} listener The user state change listener. - * @private - */ -fireauth.Auth.prototype.addUserChangeListener_ = function(listener) { - var self = this; - // Save listener. - this.userChangeListeners_.push(listener); - // Make sure redirect state is ready and then trigger listener. - this.registerPendingPromise_(this.redirectStateIsReady_.then(function() { - // Do nothing if deleted. - if (self.deleted_) { - return; - } - // Make sure listener is still subscribed. - if (goog.array.contains(self.userChangeListeners_, listener)) { - // Confirm UID change before triggering. - if (self.userStateChangeUid_ !== self.getUid()) { - self.userStateChangeUid_ = self.getUid(); - // Trigger the first call for this now that redirect state is resolved. - listener(self.getLastAccessToken_()); - } - } - })); -}; - - -/** - * Deletes the Auth instance, handling cancellation of all pending async Auth - * operations. - * @return {!Promise} - */ -fireauth.Auth.prototype.delete = function() { - this.deleted_ = true; - // Cancel all pending promises. - for (var i = 0; i < this.pendingPromises_.length; i++) { - this.pendingPromises_[i].cancel(fireauth.authenum.Error.MODULE_DESTROYED); - } - - // Empty pending promises array. - this.pendingPromises_ = []; - // Remove current user change listener. - if (this.userStorageManager_) { - this.userStorageManager_.removeCurrentUserChangeListener( - this.getSyncAuthUserChanges_); - } - // Unsubscribe from Auth event handling. - if (this.authEventManager_) { - this.authEventManager_.unsubscribe(this); - this.authEventManager_.clearRedirectResult(); - } - return Promise.resolve(); -}; - - -/** @return {boolean} Whether Auth instance has pending promises. */ -fireauth.Auth.prototype.hasPendingPromises = function() { - return this.pendingPromises_.length != 0; -}; - - -/** - * Takes in a pending promise, saves it and adds a clean up callback which - * forgets the pending promise after it is fulfilled and echoes the promise - * back. - * @param {!goog.Promise<*, *>|!goog.Promise} p The pending promise. - * @return {!goog.Promise<*, *>|!goog.Promise} - * @private - */ -fireauth.Auth.prototype.registerPendingPromise_ = function(p) { - var self = this; - // Save created promise in pending list. - this.pendingPromises_.push(p); - p.thenAlways(function() { - // When fulfilled, remove from pending list. - goog.array.remove(self.pendingPromises_, p); - }); - // Return the promise. - return p; -}; - - -/** - * Gets the list of possible sign in methods for the given email address. - * @param {string} email The email address. - * @return {!goog.Promise>} - */ -fireauth.Auth.prototype.fetchSignInMethodsForEmail = function(email) { - return /** @type {!goog.Promise>} */ ( - this.registerPendingPromise_( - this.getRpcHandler().fetchSignInMethodsForIdentifier(email))); -}; - - -/** - * @param {string} emailLink The email link. - * @return {boolean} Whether the link is a sign in with email link. - */ -fireauth.Auth.prototype.isSignInWithEmailLink = function(emailLink) { - return !!fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink); -}; - - -/** - * Sends the sign-in with email link for the email account provided. - * @param {string} email The email account to sign in with. - * @param {!Object} actionCodeSettings The action code settings object. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.sendSignInLinkToEmail = function( - email, actionCodeSettings) { - var self = this; - return this.registerPendingPromise_( - // Wrap in promise as ActionCodeSettings constructor could throw a - // synchronous error if invalid arguments are specified. - goog.Promise.resolve() - .then(function() { - var actionCodeSettingsBuilder = - new fireauth.ActionCodeSettings(actionCodeSettings); - if (!actionCodeSettingsBuilder.canHandleCodeInApp()) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - fireauth.ActionCodeSettings.RawField.HANDLE_CODE_IN_APP + - ' must be true when sending sign in link to email'); - } - return actionCodeSettingsBuilder.buildRequest(); - }).then(function(additionalRequestData) { - return self.getRpcHandler().sendSignInLinkToEmail( - email, additionalRequestData); - }).then(function(email) { - // Do not return the email. - })); -}; - - -/** - * Verifies an email action code for password reset and returns a promise - * that resolves with the associated email if verified. - * @param {string} code The email action code to verify for password reset. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.verifyPasswordResetCode = function(code) { - return this.checkActionCode(code).then(function(info) { - return info['data']['email']; - }); -}; - - -/** - * Requests resetPassword endpoint for password reset, verifies the action code - * and updates the new password, returns an empty promise. - * @param {string} code The email action code to confirm for password reset. - * @param {string} newPassword The new password. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.confirmPasswordReset = function(code, newPassword) { - return this.registerPendingPromise_( - this.getRpcHandler().confirmPasswordReset(code, newPassword) - .then(function(email) { - // Do not return the email. - })); -}; - - -/** - * Verifies an email action code and returns an empty promise if verified. - * @param {string} code The email action code to verify for password reset. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.checkActionCode = function(code) { - return this.registerPendingPromise_( - this.getRpcHandler().checkActionCode(code) - .then(function(response) { - return new fireauth.ActionCodeInfo(response); - })); -}; - - -/** - * Applies an out-of-band email action code, such as an email verification code. - * @param {string} code The email action code. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.applyActionCode = function(code) { - return this.registerPendingPromise_( - this.getRpcHandler().applyActionCode(code) - .then(function(email) { - // Returns nothing. - })); -}; - - -/** - * Sends the password reset email for the email account provided. - * @param {string} email The email account with the password to be reset. - * @param {?Object=} opt_actionCodeSettings The optional action code settings - * object. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.sendPasswordResetEmail = - function(email, opt_actionCodeSettings) { - var self = this; - return this.registerPendingPromise_( - // Wrap in promise as ActionCodeSettings constructor could throw a - // synchronous error if invalid arguments are specified. - goog.Promise.resolve().then(function() { - if (typeof opt_actionCodeSettings !== 'undefined' && - // Ignore empty objects. - !goog.object.isEmpty(opt_actionCodeSettings)) { - return new fireauth.ActionCodeSettings( - /** @type {!Object} */ (opt_actionCodeSettings)).buildRequest(); - } - return {}; - }) - .then(function(additionalRequestData) { - return self.getRpcHandler().sendPasswordResetEmail( - email, additionalRequestData); - }).then(function(email) { - // Do not return the email. - })); -}; - - -/** - * Signs in with a phone number using the app verifier instance and returns a - * promise that resolves with the confirmation result which on confirmation - * will resolve with the UserCredential object. - * @param {string} phoneNumber The phone number to authenticate with. - * @param {!firebase.auth.ApplicationVerifier} appVerifier The application - * verifier. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInWithPhoneNumber = - function(phoneNumber, appVerifier) { - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_(fireauth.ConfirmationResult.initialize( - this, - phoneNumber, - appVerifier, - // This will wait for redirectStateIsReady to resolve first. - goog.bind(this.signInWithCredential, this)))); -}; - - -/** - * Signs in a Firebase User with the provided email and the passwordless - * sign-in email link. - * @param {string} email The email account to sign in with. - * @param {?string=} opt_link The optional link which contains the OTP needed - * to complete the sign in with email link. If not specified, the current - * URL is used instead. - * @return {!goog.Promise} - */ -fireauth.Auth.prototype.signInWithEmailLink = function(email, opt_link) { - return this.registerPendingPromise_(goog.Promise.resolve().then(() => { - const link = opt_link || fireauth.util.getCurrentUrl(); - const credential = fireauth.EmailAuthProvider.credentialWithLink( - email, link); - // Check if the tenant ID in the email link matches the tenant ID on Auth - // instance. - const actionCodeUrl = - fireauth.EmailAuthProvider.getActionCodeUrlFromSignInEmailLink(link); - if (!actionCodeUrl) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, 'Invalid email link!'); - } - if (actionCodeUrl['tenantId'] !== this.getTenantId()) { - throw new fireauth.AuthError( - fireauth.authenum.Error.TENANT_ID_MISMATCH); - } - return this.signInWithCredential(credential); - })); -}; diff --git a/packages/auth/src/authcredential.js b/packages/auth/src/authcredential.js deleted file mode 100644 index ec622d9eedf..00000000000 --- a/packages/auth/src/authcredential.js +++ /dev/null @@ -1,1605 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines Auth credentials used for signInWithCredential. - */ - -goog.provide('fireauth.AuthCredential'); -goog.provide('fireauth.AuthProvider'); -goog.provide('fireauth.EmailAuthCredential'); -goog.provide('fireauth.EmailAuthProvider'); -goog.provide('fireauth.FacebookAuthProvider'); -goog.provide('fireauth.FederatedProvider'); -goog.provide('fireauth.GithubAuthProvider'); -goog.provide('fireauth.GoogleAuthProvider'); -goog.provide('fireauth.OAuthCredential'); -goog.provide('fireauth.OAuthProvider'); -goog.provide('fireauth.OAuthResponse'); -goog.provide('fireauth.PhoneAuthCredential'); -goog.provide('fireauth.PhoneAuthProvider'); -goog.provide('fireauth.SAMLAuthCredential'); -goog.provide('fireauth.SAMLAuthProvider'); -goog.provide('fireauth.TwitterAuthProvider'); - -goog.requireType('fireauth.RpcHandler'); -goog.require('fireauth.ActionCodeInfo'); -goog.require('fireauth.ActionCodeURL'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.DynamicLink'); -goog.require('fireauth.IdToken'); -goog.require('fireauth.MultiFactorAuthCredential'); -goog.require('fireauth.MultiFactorEnrollmentRequestIdentifier'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.MultiFactorSignInRequestIdentifier'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.idp'); -goog.require('fireauth.object'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Uri'); -goog.require('goog.array'); -goog.require('goog.object'); - - - -/** - * The interface that represents Auth credential. It provides the underlying - * implementation for retrieving the ID token depending on the type of - * credential. - * @interface - */ -fireauth.AuthCredential = function() {}; - - -/** - * Returns a promise to retrieve ID token using the underlying RPC handler API - * for the current credential. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @return {!goog.Promise} - * idTokenPromise The RPC handler method that returns a promise which - * resolves with an ID token. - */ -fireauth.AuthCredential.prototype.getIdTokenProvider = function(rpcHandler) {}; - - -/** - * Links the credential to an existing account, identified by an ID token. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @param {string} idToken The ID token of the existing account. - * @return {!goog.Promise} A Promise that resolves when the accounts - * are linked. - */ -fireauth.AuthCredential.prototype.linkToIdToken = - function(rpcHandler, idToken) {}; - - -/** - * Tries to match the credential's idToken with the provided UID. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @param {string} uid The UID of the user to reauthenticate. - * @return {!goog.Promise} A Promise that resolves when - * idToken UID match succeeds and returns the server response. - */ -fireauth.AuthCredential.prototype.matchIdTokenWithUid = - function(rpcHandler, uid) {}; - - -/** - * @return {!Object} The plain object representation of an Auth credential. This - * will be exposed as toJSON() externally. - */ -fireauth.AuthCredential.prototype.toPlainObject = function() {}; - - -/** - * @param {!goog.Promise} idTokenResolver A promise that resolves with - * the ID token response. - * @param {string} uid The UID to match in the token response. - * @return {!goog.Promise} A promise that resolves with the same - * response if the UID matches. - */ -fireauth.AuthCredential.verifyTokenResponseUid = - function(idTokenResolver, uid) { - return idTokenResolver.then(function(response) { - // This should not happen as rpcHandler verifyAssertion and verifyPassword - // always guarantee an ID token is available. - if (response[fireauth.RpcHandler.AuthServerField.ID_TOKEN]) { - // Parse the token object. - var parsedIdToken = fireauth.IdToken.parse( - response[fireauth.RpcHandler.AuthServerField.ID_TOKEN]); - // Confirm token localId matches the provided UID. If not, throw the user - // mismatch error. - if (!parsedIdToken || uid != parsedIdToken.getLocalId()) { - throw new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH); - } - return response; - } - throw new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH); - }) - .thenCatch(function(error) { - // Translate auth/user-not-found error directly to auth/user-mismatch. - throw fireauth.AuthError.translateError( - error, - fireauth.authenum.Error.USER_DELETED, - fireauth.authenum.Error.USER_MISMATCH); - }); -}; - - - -/** - * The interface that represents the Auth provider. - * @interface - */ -fireauth.AuthProvider = function() {}; - - -/** - * @param {...*} var_args The credential data. - * @return {!fireauth.AuthCredential} The Auth provider credential. - */ -fireauth.AuthProvider.credential; - - -/** - * @typedef {{ - * accessToken: (?string|undefined), - * idToken: (?string|undefined), - * nonce: (?string|undefined), - * oauthToken: (?string|undefined), - * oauthTokenSecret: (?string|undefined), - * pendingToken: (?string|undefined) - * }} - */ -fireauth.OAuthResponse; - - -/** - * The SAML Auth credential class. The Constructor is not publicly visible. - * This is constructed by the SDK on successful or failure after SAML sign-in - * and returned to developer. - * @param {!fireauth.idp.ProviderId} providerId The provider ID. - * @param {string} pendingToken The SAML response pending token. - * @constructor - * @implements {fireauth.AuthCredential} - */ -fireauth.SAMLAuthCredential = function(providerId, pendingToken) { - if (pendingToken) { - /** @private {string} The pending token where SAML response is encrypted. */ - this.pendingToken_ = pendingToken; - } else { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'failed to construct a credential'); - } - - fireauth.object.setReadonlyProperty(this, 'providerId', providerId); - fireauth.object.setReadonlyProperty(this, 'signInMethod', providerId); -}; - - -/** - * Returns a promise to retrieve ID token using the underlying RPC handler API - * for the current credential. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @return {!goog.Promise} - * idTokenPromise The RPC handler method that returns a promise which - * resolves with an ID token. - * @override - */ -fireauth.SAMLAuthCredential.prototype.getIdTokenProvider = - function(rpcHandler) { - return rpcHandler.verifyAssertion( - /** @type {!fireauth.RpcHandler.VerifyAssertionData} */ ( - this.makeVerifyAssertionRequest_())); -}; - - -/** - * Links the credential to an existing account, identified by an ID token. - * @param {!fireauth.RpcHandler} rpcHandler The rpc handler. - * @param {string} idToken The ID token of the existing account. - * @return {!goog.Promise} A Promise that resolves when the accounts - * are linked, returning the backend response. - * @override - */ -fireauth.SAMLAuthCredential.prototype.linkToIdToken = - function(rpcHandler, idToken) { - var request = this.makeVerifyAssertionRequest_(); - request['idToken'] = idToken; - return rpcHandler.verifyAssertionForLinking( - /** @type {!fireauth.RpcHandler.VerifyAssertionData} */ (request)); -}; - - -/** - * Tries to match the credential's idToken with the provided UID. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @param {string} uid The UID of the user to reauthenticate. - * @return {!goog.Promise} A Promise that resolves when - * idToken UID match succeeds and returns the server response. - * @override - */ -fireauth.SAMLAuthCredential.prototype.matchIdTokenWithUid = - function(rpcHandler, uid) { - var request = this.makeVerifyAssertionRequest_(); - // Do not create a new account if the user doesn't exist. - return fireauth.AuthCredential.verifyTokenResponseUid( - rpcHandler.verifyAssertionForExisting( - /** @type {!fireauth.RpcHandler.VerifyAssertionData} */ (request)), - uid); -}; - - -/** - * @return {!Object} A request to the VerifyAssertion endpoint, populated with - * the assertion data from this credential. - * @private - */ -fireauth.SAMLAuthCredential.prototype.makeVerifyAssertionRequest_ = - function() { - return { - 'pendingToken': this.pendingToken_, - // Always use http://localhost. - 'requestUri': 'http://localhost' - }; -}; - - -/** - * @return {!Object} The plain object representation of an Auth credential. - * @override - */ -fireauth.SAMLAuthCredential.prototype.toPlainObject = function() { - return { - 'providerId': this['providerId'], - 'signInMethod': this['signInMethod'], - 'pendingToken': this.pendingToken_ - }; -}; - - -/** - * @param {?Object|undefined} json The plain object representation of a - * SAMLAuthCredential. - * @return {?fireauth.SAMLAuthCredential} The SAML credential if the object - * is a JSON representation of a SAMLAuthCredential, null otherwise. - */ -fireauth.SAMLAuthCredential.fromJSON = function(json) { - if (json && - json['providerId'] && - json['signInMethod'] && - json['providerId'].indexOf(fireauth.constants.SAML_PREFIX) == 0 && - json['pendingToken']) { - try { - return new fireauth.SAMLAuthCredential( - json['providerId'], json['pendingToken']); - } catch (e) { - return null; - } - } - return null; -}; - - -/** - * The OAuth credential class. - * @param {!fireauth.idp.ProviderId} providerId The provider ID. - * @param {!fireauth.OAuthResponse} oauthResponse The OAuth - * response object containing token information. - * @param {!fireauth.idp.SignInMethod} signInMethod The sign in method. - * @constructor - * @implements {fireauth.AuthCredential} - */ -fireauth.OAuthCredential = function(providerId, oauthResponse, signInMethod) { - /** - * @private {?string} The pending token where the IdP response is encrypted. - */ - this.pendingToken_ = null; - if (oauthResponse['idToken'] || oauthResponse['accessToken']) { - // OAuth 2 and either ID token or access token. - if (oauthResponse['idToken']) { - fireauth.object.setReadonlyProperty( - this, 'idToken', oauthResponse['idToken']); - } - if (oauthResponse['accessToken']) { - fireauth.object.setReadonlyProperty( - this, 'accessToken', oauthResponse['accessToken']); - } - // Add nonce if available and no pendingToken is present. - if (oauthResponse['nonce'] && !oauthResponse['pendingToken']) { - fireauth.object.setReadonlyProperty( - this, 'nonce', oauthResponse['nonce']); - } - if (oauthResponse['pendingToken']) { - this.pendingToken_ = oauthResponse['pendingToken']; - } - } else if (oauthResponse['oauthToken'] && - oauthResponse['oauthTokenSecret']) { - // OAuth 1 and OAuth token with OAuth token secret. - fireauth.object.setReadonlyProperty( - this, 'accessToken', oauthResponse['oauthToken']); - fireauth.object.setReadonlyProperty( - this, 'secret', oauthResponse['oauthTokenSecret']); - } else { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'failed to construct a credential'); - } - - fireauth.object.setReadonlyProperty(this, 'providerId', providerId); - fireauth.object.setReadonlyProperty(this, 'signInMethod', signInMethod); -}; - - -/** - * Returns a promise to retrieve ID token using the underlying RPC handler API - * for the current credential. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @return {!goog.Promise} - * idTokenPromise The RPC handler method that returns a promise which - * resolves with an ID token. - * @override - */ -fireauth.OAuthCredential.prototype.getIdTokenProvider = function(rpcHandler) { - return rpcHandler.verifyAssertion( - /** @type {!fireauth.RpcHandler.VerifyAssertionData} */ ( - this.makeVerifyAssertionRequest_())); -}; - - -/** - * Links the credential to an existing account, identified by an ID token. - * @param {!fireauth.RpcHandler} rpcHandler The rpc handler. - * @param {string} idToken The ID token of the existing account. - * @return {!goog.Promise} A Promise that resolves when the accounts - * are linked, returning the backend response. - * @override - */ -fireauth.OAuthCredential.prototype.linkToIdToken = - function(rpcHandler, idToken) { - var request = this.makeVerifyAssertionRequest_(); - request['idToken'] = idToken; - return rpcHandler.verifyAssertionForLinking( - /** @type {!fireauth.RpcHandler.VerifyAssertionData} */ (request)); -}; - - -/** - * Tries to match the credential's idToken with the provided UID. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @param {string} uid The UID of the user to reauthenticate. - * @return {!goog.Promise} A Promise that resolves when - * idToken UID match succeeds and returns the server response. - * @override - */ -fireauth.OAuthCredential.prototype.matchIdTokenWithUid = - function(rpcHandler, uid) { - var request = this.makeVerifyAssertionRequest_(); - // Do not create a new account if the user doesn't exist. - return fireauth.AuthCredential.verifyTokenResponseUid( - rpcHandler.verifyAssertionForExisting( - /** @type {!fireauth.RpcHandler.VerifyAssertionData} */ (request)), - uid); -}; - - -/** - * @return {!Object} A request to the VerifyAssertion endpoint, populated with - * the OAuth data from this credential. - * @private - */ -fireauth.OAuthCredential.prototype.makeVerifyAssertionRequest_ = function() { - var postBody = {}; - if (this['idToken']) { - postBody['id_token'] = this['idToken']; - } - if (this['accessToken']) { - postBody['access_token'] = this['accessToken']; - } - if (this['secret']) { - postBody['oauth_token_secret'] = this['secret']; - } - postBody['providerId'] = this['providerId']; - // Pass nonce in postBody if available. - if (this['nonce'] && !this.pendingToken_) { - postBody['nonce'] = this['nonce']; - } - var request = { - 'postBody': goog.Uri.QueryData.createFromMap(postBody).toString(), - // Always use http://localhost. - 'requestUri': 'http://localhost' - }; - if (this.pendingToken_) { - // For pendingToken, just pass it through and drop postBody. - delete request['postBody']; - request['pendingToken'] = this.pendingToken_; - } - return request; -}; - - -/** - * @return {!Object} The plain object representation of an Auth credential. - * @override - */ -fireauth.OAuthCredential.prototype.toPlainObject = function() { - var obj = { - 'providerId': this['providerId'], - 'signInMethod': this['signInMethod'] - }; - if (this['idToken']) { - obj['oauthIdToken'] = this['idToken']; - } - if (this['accessToken']) { - obj['oauthAccessToken'] = this['accessToken']; - } - if (this['secret']) { - obj['oauthTokenSecret'] = this['secret']; - } - if (this['nonce']) { - obj['nonce'] = this['nonce']; - } - if (this.pendingToken_) { - obj['pendingToken'] = this.pendingToken_; - } - return obj; -}; - - -/** - * @param {?Object|undefined} json The plain object representation of an - * OAuthCredential. - * @return {?fireauth.OAuthCredential} The OAuth/OIDC credential if the object - * is a JSON representation of an OAuthCredential, null otherwise. - */ -fireauth.OAuthCredential.fromJSON = function(json) { - if (json && - json['providerId'] && - json['signInMethod']) { - // Convert to OAuthResponse format. - var oauthResponse = { - // OIDC && google.com. - 'idToken': json['oauthIdToken'], - // OAuth 2.0 providers. - 'accessToken': json['oauthTokenSecret'] ? null : json['oauthAccessToken'], - // OAuth 1.0 provider, eg. Twitter. - 'oauthTokenSecret': json['oauthTokenSecret'], - 'oauthToken': json['oauthTokenSecret'] && json['oauthAccessToken'], - 'nonce': json['nonce'], - 'pendingToken': json['pendingToken'] - }; - try { - // Constructor will validate the OAuthResponse. - return new fireauth.OAuthCredential( - json['providerId'], oauthResponse, json['signInMethod']); - } catch (e) { - return null; - } - } - return null; -}; - - -/** - * A generic OAuth provider (OAuth1 or OAuth2). - * @param {string} providerId The IdP provider ID (e.g. google.com, - * facebook.com) registered with the backend. - * @param {?Array=} opt_reservedParams The backlist of parameters that - * cannot be set through setCustomParameters. - * @constructor - */ -fireauth.FederatedProvider = function(providerId, opt_reservedParams) { - /** @private {!Array} */ - this.reservedParams_ = opt_reservedParams || []; - - // Set read only instance providerId property. - // Set read only instance isOAuthProvider property. - fireauth.object.setReadonlyProperties(this, { - 'providerId': providerId, - 'isOAuthProvider': true - }); - - /** @private {!Object} The OAuth custom parameters for current provider. */ - this.customParameters_ = {}; - /** @protected {?string} The custom OAuth language parameter. */ - this.languageParameter = - (fireauth.idp.getIdpSettings(/** @type {!fireauth.idp.ProviderId} */ ( - providerId)) || {}).languageParam || null; - /** @protected {?string} The default language. */ - this.defaultLanguageCode = null; -}; - -/** - * @param {!Object} customParameters The custom OAuth parameters to pass - * in OAuth request. - * @return {!fireauth.FederatedProvider} The FederatedProvider instance, for - * chaining method calls. - */ -fireauth.FederatedProvider.prototype.setCustomParameters = - function(customParameters) { - this.customParameters_ = goog.object.clone(customParameters); - return this; -}; - - -/** - * Set the default language code on the provider instance. - * @param {?string} languageCode The default language code to set if not already - * provided in the custom parameters. - */ -fireauth.FederatedProvider.prototype.setDefaultLanguage = - function(languageCode) { - this.defaultLanguageCode = languageCode; -}; - - -/** - * @return {!Object} The custom OAuth parameters to pass in OAuth request. - */ -fireauth.FederatedProvider.prototype.getCustomParameters = function() { - // The backend already checks for these values and makes sure no reserved - // fields like client ID, redirect URI, state are overwritten by these - // fields. - var params = - fireauth.util.copyWithoutNullsOrUndefined(this.customParameters_); - // Convert to strings. - for (var key in params) { - params[key] = params[key].toString(); - } - // Remove blacklisted OAuth custom parameters. - var customParams = - fireauth.util.removeEntriesWithKeys(params, this.reservedParams_); - // If language param supported and not already provided, use default language. - if (this.languageParameter && - this.defaultLanguageCode && - !customParams[this.languageParameter]) { - customParams[this.languageParameter] = this.defaultLanguageCode; - } - return customParams; -}; - - -/** - * Generic SAML auth provider. - * @param {string} providerId The SAML IdP provider ID (e.g. saml.saml2rp) - * registered with the backend. - * @constructor - * @extends {fireauth.FederatedProvider} - * @implements {fireauth.AuthProvider} - */ -fireauth.SAMLAuthProvider = function(providerId) { - // SAML provider IDs must be prefixed with the SAML_PREFIX. - if (!fireauth.idp.isSaml(providerId)) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'SAML provider IDs must be prefixed with "' + - fireauth.constants.SAML_PREFIX + '"'); - } - // isOAuthProvider is true even though this is not an OAuth provider. - // This can be confusing as this is a SAML provider. However, this property - // is needed to allow signInWithPopup/Redirect. We should rename it to - // something more accurate: isFederatedProvider. - fireauth.SAMLAuthProvider.base(this, 'constructor', providerId, []); -}; -goog.inherits(fireauth.SAMLAuthProvider, fireauth.FederatedProvider); - - -/** - * Generic OAuth2 Auth provider. - * @param {string} providerId The IdP provider ID (e.g. google.com, - * facebook.com) registered with the backend. - * @constructor - * @extends {fireauth.FederatedProvider} - * @implements {fireauth.AuthProvider} - */ -fireauth.OAuthProvider = function(providerId) { - fireauth.OAuthProvider.base(this, 'constructor', providerId, - fireauth.idp.RESERVED_OAUTH2_PARAMS); - - /** @private {!Array} The list of OAuth2 scopes to request. */ - this.scopes_ = []; -}; -goog.inherits(fireauth.OAuthProvider, fireauth.FederatedProvider); - - -/** - * @param {string} scope The OAuth scope to request. - * @return {!fireauth.OAuthProvider} The OAuthProvider instance, for chaining - * method calls. - */ -fireauth.OAuthProvider.prototype.addScope = function(scope) { - // If not already added, add scope to list. - if (!goog.array.contains(this.scopes_, scope)) { - this.scopes_.push(scope); - } - return this; -}; - - -/** @return {!Array} The Auth provider's list of scopes. */ -fireauth.OAuthProvider.prototype.getScopes = function() { - return goog.array.clone(this.scopes_); -}; - - -/** - * Initializes an OAuth AuthCredential. At least one of ID token or access token - * must be defined. When providing an OIDC ID token with a nonce encoded, the - * raw nonce must also be provided. - * @param {?Object|string} optionsOrIdToken Either the options object containing - * the ID token, access token and raw nonce or the ID token string. - * @param {?string=} opt_accessToken The optional OAuth access token. - * @return {!fireauth.AuthCredential} The Auth credential object. - */ -fireauth.OAuthProvider.prototype.credential = - function(optionsOrIdToken, opt_accessToken) { - var oauthResponse; - if (goog.isObject(optionsOrIdToken)) { - oauthResponse = { - 'idToken': optionsOrIdToken['idToken'] || null, - 'accessToken': optionsOrIdToken['accessToken'] || null, - 'nonce': optionsOrIdToken['rawNonce'] || null - }; - } else { - oauthResponse = { - 'idToken': optionsOrIdToken || null, - 'accessToken': opt_accessToken || null - }; - } - if (!oauthResponse['idToken'] && !oauthResponse['accessToken']) { - throw new fireauth.AuthError(fireauth.authenum.Error.ARGUMENT_ERROR, - 'credential failed: must provide the ID token and/or the access ' + - 'token.'); - } - // For OAuthCredential, sign in method is same as providerId. - return new fireauth.OAuthCredential(this['providerId'], - oauthResponse, - this['providerId']); -}; - - -/** - * Facebook Auth provider. - * @constructor - * @extends {fireauth.OAuthProvider} - * @implements {fireauth.AuthProvider} - */ -fireauth.FacebookAuthProvider = function() { - fireauth.FacebookAuthProvider.base(this, 'constructor', - fireauth.idp.ProviderId.FACEBOOK); -}; -goog.inherits(fireauth.FacebookAuthProvider, fireauth.OAuthProvider); - -fireauth.object.setReadonlyProperty(fireauth.FacebookAuthProvider, - 'PROVIDER_ID', fireauth.idp.ProviderId.FACEBOOK); - -fireauth.object.setReadonlyProperty(fireauth.FacebookAuthProvider, - 'FACEBOOK_SIGN_IN_METHOD', fireauth.idp.SignInMethod.FACEBOOK); - - -/** - * Initializes a Facebook AuthCredential. - * @param {string} accessTokenOrObject The Facebook access token, or object - * containing the token for FirebaseUI backwards compatibility. - * @return {!fireauth.AuthCredential} The Auth credential object. - */ -fireauth.FacebookAuthProvider.credential = function(accessTokenOrObject) { - if (!accessTokenOrObject) { - throw new fireauth.AuthError(fireauth.authenum.Error.ARGUMENT_ERROR, - 'credential failed: expected 1 argument (the OAuth access token).'); - } - var accessToken = accessTokenOrObject; - if (goog.isObject(accessTokenOrObject)) { - accessToken = accessTokenOrObject['accessToken']; - } - return new fireauth.FacebookAuthProvider().credential({ - 'accessToken': /** @type {string} */ (accessToken) - }); -}; - - -/** - * GitHub Auth provider. - * @constructor - * @extends {fireauth.OAuthProvider} - * @implements {fireauth.AuthProvider} - */ -fireauth.GithubAuthProvider = function() { - fireauth.GithubAuthProvider.base(this, 'constructor', - fireauth.idp.ProviderId.GITHUB); -}; -goog.inherits(fireauth.GithubAuthProvider, fireauth.OAuthProvider); - -fireauth.object.setReadonlyProperty(fireauth.GithubAuthProvider, - 'PROVIDER_ID', fireauth.idp.ProviderId.GITHUB); - -fireauth.object.setReadonlyProperty(fireauth.GithubAuthProvider, - 'GITHUB_SIGN_IN_METHOD', fireauth.idp.SignInMethod.GITHUB); - - -/** - * Initializes a GitHub AuthCredential. - * @param {string} accessTokenOrObject The GitHub access token, or object - * containing the token for FirebaseUI backwards compatibility. - * @return {!fireauth.AuthCredential} The Auth credential object. - */ -fireauth.GithubAuthProvider.credential = function(accessTokenOrObject) { - if (!accessTokenOrObject) { - throw new fireauth.AuthError(fireauth.authenum.Error.ARGUMENT_ERROR, - 'credential failed: expected 1 argument (the OAuth access token).'); - } - var accessToken = accessTokenOrObject; - if (goog.isObject(accessTokenOrObject)) { - accessToken = accessTokenOrObject['accessToken']; - } - return new fireauth.GithubAuthProvider().credential({ - 'accessToken': /** @type {string} */ (accessToken) - }); -}; - - -/** - * Google Auth provider. - * @constructor - * @extends {fireauth.OAuthProvider} - * @implements {fireauth.AuthProvider} - */ -fireauth.GoogleAuthProvider = function() { - fireauth.GoogleAuthProvider.base(this, 'constructor', - fireauth.idp.ProviderId.GOOGLE); - - // Add profile scope to Google Auth provider as default scope. - // This is to ensure profile info is populated in current user. - this.addScope('profile'); -}; -goog.inherits(fireauth.GoogleAuthProvider, fireauth.OAuthProvider); - -fireauth.object.setReadonlyProperty(fireauth.GoogleAuthProvider, - 'PROVIDER_ID', fireauth.idp.ProviderId.GOOGLE); - -fireauth.object.setReadonlyProperty(fireauth.GoogleAuthProvider, - 'GOOGLE_SIGN_IN_METHOD', fireauth.idp.SignInMethod.GOOGLE); - - -/** - * Initializes a Google AuthCredential. - * @param {?string=} idTokenOrObject The Google ID token. If null or undefined, - * we expect the access token to be passed. It can also be an object - * containing the tokens for FirebaseUI backwards compatibility. - * @param {?string=} accessToken The Google access token. If null or - * undefined, we expect the ID token to have been passed. - * @return {!fireauth.AuthCredential} The Auth credential object. - */ -fireauth.GoogleAuthProvider.credential = - function(idTokenOrObject, accessToken) { - var idToken = idTokenOrObject; - if (goog.isObject(idTokenOrObject)) { - idToken = idTokenOrObject['idToken']; - accessToken = idTokenOrObject['accessToken']; - } - return new fireauth.GoogleAuthProvider().credential({ - 'idToken': /** @type {string} */ (idToken), - 'accessToken': /** @type {string} */ (accessToken) - }); -}; - - -/** - * Twitter Auth provider. - * @constructor - * @extends {fireauth.FederatedProvider} - * @implements {fireauth.AuthProvider} - */ -fireauth.TwitterAuthProvider = function() { - fireauth.TwitterAuthProvider.base(this, 'constructor', - fireauth.idp.ProviderId.TWITTER, - fireauth.idp.RESERVED_OAUTH1_PARAMS); -}; -goog.inherits(fireauth.TwitterAuthProvider, fireauth.FederatedProvider); - -fireauth.object.setReadonlyProperty(fireauth.TwitterAuthProvider, - 'PROVIDER_ID', fireauth.idp.ProviderId.TWITTER); - -fireauth.object.setReadonlyProperty(fireauth.TwitterAuthProvider, - 'TWITTER_SIGN_IN_METHOD', fireauth.idp.SignInMethod.TWITTER); - - -/** - * Initializes a Twitter AuthCredential. - * @param {string} tokenOrObject The Twitter access token, or object - * containing the token for FirebaseUI backwards compatibility. - * @param {string} secret The Twitter secret. - * @return {!fireauth.AuthCredential} The Auth credential object. - */ -fireauth.TwitterAuthProvider.credential = function(tokenOrObject, secret) { - var tokenObject = tokenOrObject; - if (!goog.isObject(tokenObject)) { - tokenObject = { - 'oauthToken': tokenOrObject, - 'oauthTokenSecret': secret - }; - } - - if (!tokenObject['oauthToken'] || !tokenObject['oauthTokenSecret']) { - throw new fireauth.AuthError(fireauth.authenum.Error.ARGUMENT_ERROR, - 'credential failed: expected 2 arguments (the OAuth access token ' + - 'and secret).'); - } - - return new fireauth.OAuthCredential(fireauth.idp.ProviderId.TWITTER, - /** @type {!fireauth.OAuthResponse} */ (tokenObject), - fireauth.idp.SignInMethod.TWITTER); -}; - - -/** - * The email and password credential class. - * @param {string} email The credential email. - * @param {string} password The credential password. - * @param {string=} opt_signInMethod The credential sign in method can be either - * 'password' or 'emailLink' - * @constructor - * @implements {fireauth.AuthCredential} - */ -fireauth.EmailAuthCredential = function(email, password, opt_signInMethod) { - this.email_ = email; - this.password_ = password; - fireauth.object.setReadonlyProperty(this, 'providerId', - fireauth.idp.ProviderId.PASSWORD); - var signInMethod = opt_signInMethod === - fireauth.EmailAuthProvider['EMAIL_LINK_SIGN_IN_METHOD'] ? - fireauth.EmailAuthProvider['EMAIL_LINK_SIGN_IN_METHOD'] : - fireauth.EmailAuthProvider['EMAIL_PASSWORD_SIGN_IN_METHOD']; - fireauth.object.setReadonlyProperty(this, 'signInMethod', signInMethod); -}; - - -/** - * Returns a promise to retrieve ID token using the underlying RPC handler API - * for the current credential. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @return {!goog.Promise} - * idTokenPromise The RPC handler method that returns a promise which - * resolves with an ID token. - * @override - */ -fireauth.EmailAuthCredential.prototype.getIdTokenProvider = - function(rpcHandler) { - if (this['signInMethod'] == - fireauth.EmailAuthProvider['EMAIL_LINK_SIGN_IN_METHOD']) { - return rpcHandler.emailLinkSignIn(this.email_, this.password_); - } - return rpcHandler.verifyPassword(this.email_, this.password_); -}; - - -/** - * Adds an email and password account to an existing account, identified by an - * ID token. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @param {string} idToken The ID token of the existing account. - * @return {!goog.Promise} A Promise that resolves when the accounts - * are linked, returning the backend response. - * @override - */ -fireauth.EmailAuthCredential.prototype.linkToIdToken = - function(rpcHandler, idToken) { - if (this['signInMethod'] == - fireauth.EmailAuthProvider['EMAIL_LINK_SIGN_IN_METHOD']) { - return rpcHandler.emailLinkSignInForLinking( - idToken, this.email_, this.password_); - } - return rpcHandler.updateEmailAndPassword( - idToken, this.email_, this.password_); -}; - - -/** - * Tries to match the credential's idToken with the provided UID. - * @param {!fireauth.RpcHandler} rpcHandler The rpc handler. - * @param {string} uid The UID of the user to reauthenticate. - * @return {!goog.Promise} A Promise that resolves when - * reauthentication succeeds. - * @override - */ -fireauth.EmailAuthCredential.prototype.matchIdTokenWithUid = - function(rpcHandler, uid) { - // Do not create a new account if the user doesn't exist. - return fireauth.AuthCredential.verifyTokenResponseUid( - // This shouldn't create a new email/password account. - this.getIdTokenProvider(rpcHandler), - uid); -}; - - -/** - * @return {!Object} The plain object representation of an Auth credential. - * @override - */ -fireauth.EmailAuthCredential.prototype.toPlainObject = function() { - return { - 'email': this.email_, - 'password': this.password_, - 'signInMethod': this['signInMethod'] - }; -}; - - -/** - * @param {?Object|undefined} json The plain object representation of a - * EmailAuthCredential. - * @return {?fireauth.EmailAuthCredential} The email credential if the object - * is a JSON representation of an EmailAuthCredential, null otherwise. - */ -fireauth.EmailAuthCredential.fromJSON = function(json) { - if (json && json['email'] && json['password']) { - return new fireauth.EmailAuthCredential( - json['email'], - json['password'], - json['signInMethod']); - } - return null; -}; - - -/** - * Email password Auth provider implementation. - * @constructor - * @implements {fireauth.AuthProvider} - */ -fireauth.EmailAuthProvider = function() { - // Set read-only instance providerId and isOAuthProvider property. - fireauth.object.setReadonlyProperties(this, { - 'providerId': fireauth.idp.ProviderId.PASSWORD, - 'isOAuthProvider': false - }); -}; - - -/** - * Initializes an instance of an email/password Auth credential. - * @param {string} email The credential email. - * @param {string} password The credential password. - * @return {!fireauth.EmailAuthCredential} The Auth credential object. - */ -fireauth.EmailAuthProvider.credential = function(email, password) { - return new fireauth.EmailAuthCredential(email, password); -}; - - -/** - * @param {string} email The credential email. - * @param {string} emailLink The credential email link. - * @return {!fireauth.EmailAuthCredential} The Auth credential object. - */ -fireauth.EmailAuthProvider.credentialWithLink = function(email, emailLink) { - var actionCodeUrl = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink); - if (!actionCodeUrl) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, 'Invalid email link!'); - } - return new fireauth.EmailAuthCredential(email, actionCodeUrl['code'], - fireauth.EmailAuthProvider['EMAIL_LINK_SIGN_IN_METHOD']); -}; - - -/** - * @param {string} emailLink The sign in email link to be validated. - * @return {?fireauth.ActionCodeURL} The sign in email link action code URL. - * Returns null if the email link is invalid. - */ -fireauth.EmailAuthProvider.getActionCodeUrlFromSignInEmailLink = - function(emailLink) { - emailLink = fireauth.DynamicLink.parseDeepLink(emailLink); - var actionCodeUrl = fireauth.ActionCodeURL.parseLink(emailLink); - if (actionCodeUrl && (actionCodeUrl['operation'] === - fireauth.ActionCodeInfo.Operation.EMAIL_SIGNIN)) { - return actionCodeUrl; - } - return null; -}; - - -// Set read only PROVIDER_ID property. -fireauth.object.setReadonlyProperties(fireauth.EmailAuthProvider, { - 'PROVIDER_ID': fireauth.idp.ProviderId.PASSWORD -}); - -// Set read only EMAIL_LINK_SIGN_IN_METHOD property. -fireauth.object.setReadonlyProperties(fireauth.EmailAuthProvider, { - 'EMAIL_LINK_SIGN_IN_METHOD': fireauth.idp.SignInMethod.EMAIL_LINK -}); - -// Set read only EMAIL_PASSWORD_SIGN_IN_METHOD property. -fireauth.object.setReadonlyProperties(fireauth.EmailAuthProvider, { - 'EMAIL_PASSWORD_SIGN_IN_METHOD': fireauth.idp.SignInMethod.EMAIL_PASSWORD -}); - - -/** - * A credential for phone number sign-in. Phone credentials can also be used as - * second factor assertions. - * A `PhoneAuthCredential` is also a `MultiFactorAuthCredential`. A - * `PhoneMultiFactorAssertion` requires a `PhoneAuthCredential`. - * @param {!fireauth.PhoneAuthCredential.Parameters_} params The credential - * parameters that prove the user owns the claimed phone number. - * @constructor - * @implements {fireauth.MultiFactorAuthCredential} - */ -fireauth.PhoneAuthCredential = function(params) { - // Either verification ID and code, or phone number temporary proof must be - // provided. - if (!(params.verificationId && params.verificationCode) && - !(params.temporaryProof && params.phoneNumber)) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - - /** - * The phone Auth parameters that prove ownership of a phone number, either - * through completion of a phone verification flow, or by referencing a - * previously completed verification flow ("temporaryProof"). - * @private {!fireauth.PhoneAuthCredential.Parameters_} - */ - this.params_ = params; - - fireauth.object.setReadonlyProperty(this, 'providerId', - fireauth.idp.ProviderId.PHONE); - /** - * @public {string} The provider ID required by the - * `fireauth.MultiFactorAuthCredential` interface. - */ - this.providerId = fireauth.idp.ProviderId.PHONE; - - fireauth.object.setReadonlyProperty( - this, 'signInMethod', fireauth.idp.SignInMethod.PHONE); -}; - - -/** - * Parameters that prove ownership of a phone number via a ID "verificationId" - * of a request to send a code to the phone number, with the code - * "verificationCode" that the user received on their phone. - * @private - * @typedef {{ - * verificationId: string, - * verificationCode: string - * }} - */ -fireauth.PhoneAuthCredential.VerificationParameters_; - - -/** - * Parameters that prove ownership of a phone number by referencing a previously - * completed phone Auth flow. - * @private - * @typedef {{ - * temporaryProof: string, - * phoneNumber: string - * }} - */ -fireauth.PhoneAuthCredential.TemporaryProofParameters_; - - -/** - * @private - * @typedef { - * !fireauth.PhoneAuthCredential.VerificationParameters_| - * !fireauth.PhoneAuthCredential.TemporaryProofParameters_ - * } - */ -fireauth.PhoneAuthCredential.Parameters_; - - -/** - * Retrieves an ID token from the backend given the current credential. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @return {!goog.Promise} A Promise that resolves with the - * backend response. - * @override - */ -fireauth.PhoneAuthCredential.prototype.getIdTokenProvider = - function(rpcHandler) { - return rpcHandler.verifyPhoneNumber(this.makeVerifyPhoneNumberRequest_()); -}; - - -/** - * Adds a phone credential to an existing account identified by an ID token. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @param {string} idToken The ID token of the existing account. - * @return {!goog.Promise} A Promise that resolves when the accounts - * are linked, returning the backend response. - * @override - */ -fireauth.PhoneAuthCredential.prototype.linkToIdToken = - function(rpcHandler, idToken) { - var request = this.makeVerifyPhoneNumberRequest_(); - request['idToken'] = idToken; - return rpcHandler.verifyPhoneNumberForLinking(request); -}; - - -/** - * Tries to match the credential's idToken with the provided UID. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler. - * @param {string} uid The UID of the user to reauthenticate. - * @return {!goog.Promise} A Promise that resolves when - * reauthentication succeeds. - * @override - */ -fireauth.PhoneAuthCredential.prototype.matchIdTokenWithUid = - function(rpcHandler, uid) { - var request = this.makeVerifyPhoneNumberRequest_(); - return fireauth.AuthCredential.verifyTokenResponseUid( - rpcHandler.verifyPhoneNumberForExisting(request), - uid); -}; - - -/** - * Converts a PhoneAuthCredential to a plain object. - * @return {!Object} - * @override - */ -fireauth.PhoneAuthCredential.prototype.toPlainObject = function() { - var obj = { - 'providerId': fireauth.idp.ProviderId.PHONE - }; - if (this.params_.verificationId) { - obj['verificationId'] = this.params_.verificationId; - } - if (this.params_.verificationCode) { - obj['verificationCode'] = this.params_.verificationCode; - } - if (this.params_.temporaryProof) { - obj['temporaryProof'] = this.params_.temporaryProof; - } - if (this.params_.phoneNumber) { - obj['phoneNumber'] = this.params_.phoneNumber; - } - return obj; -}; - - -/** - * @param {?Object|undefined} json The plain object representation of a - * PhoneAuthCredential. - * @return {?fireauth.PhoneAuthCredential} The phone credential if the object - * is a JSON representation of an PhoneAuthCredential, null otherwise. - */ -fireauth.PhoneAuthCredential.fromJSON = function(json) { - if (json && - json['providerId'] === fireauth.idp.ProviderId.PHONE && - ((json['verificationId'] && json['verificationCode']) || - (json['temporaryProof'] && json['phoneNumber']))) { - var params = {}; - var allowedKeys = [ - 'verificationId', 'verificationCode', 'temporaryProof', 'phoneNumber' - ]; - goog.array.forEach(allowedKeys, function(key) { - if (json[key]) { - params[key] = json[key]; - } - }); - return new fireauth.PhoneAuthCredential( - /** @type {!fireauth.PhoneAuthCredential.Parameters_} */ (params)); - } - return null; -}; - - -/** - * @return {!Object} A request to the verifyPhoneNumber endpoint based on the - * current state of the object. - * @private - */ -fireauth.PhoneAuthCredential.prototype.makeVerifyPhoneNumberRequest_ = - function() { - if (this.params_.temporaryProof && this.params_.phoneNumber) { - return { - 'temporaryProof': this.params_.temporaryProof, - 'phoneNumber': this.params_.phoneNumber - }; - } - - return { - 'sessionInfo': this.params_.verificationId, - 'code': this.params_.verificationCode - }; -}; - - -/** - * Finalizes the 2nd factor enrollment flow with the current AuthCredential - * using the enrollment request identifier. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorEnrollmentRequestIdentifier} enrollmentRequest - * The enrollment request identifying the user. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the updated ID and refresh tokens. - * @override - */ -fireauth.PhoneAuthCredential.prototype.finalizeMfaEnrollment = - function(rpcHandler, enrollmentRequest) { - goog.object.extend( - enrollmentRequest, - { - 'phoneVerificationInfo': this.makeVerifyPhoneNumberRequest_() - }); - return /** @type {!goog.Promise<{idToken: string, refreshToken: string}>} */ ( - rpcHandler.finalizePhoneMfaEnrollment(enrollmentRequest)); -}; - - -/** - * Finalizes the 2nd factor sign-in flow with the current AuthCredential - * using the sign-in request identifier. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorSignInRequestIdentifier} signInRequest - * The sign-in request identifying the user. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the signed in user's ID and refresh tokens. - * @override - */ -fireauth.PhoneAuthCredential.prototype.finalizeMfaSignIn = - function(rpcHandler, signInRequest) { - goog.object.extend( - signInRequest, - { - 'phoneVerificationInfo': this.makeVerifyPhoneNumberRequest_() - }); - return /** @type {!goog.Promise<{idToken: string, refreshToken: string}>} */ ( - rpcHandler.finalizePhoneMfaSignIn(signInRequest)); -}; - - -/** - * Phone Auth provider implementation. - * @param {?fireauth.Auth=} opt_auth The Firebase Auth instance. - * @constructor - * @implements {fireauth.AuthProvider} - */ -fireauth.PhoneAuthProvider = function(opt_auth) { - try { - /** @private {!fireauth.Auth} */ - this.auth_ = opt_auth || firebase['auth'](); - } catch (e) { - throw new fireauth.AuthError(fireauth.authenum.Error.ARGUMENT_ERROR, - 'Either an instance of firebase.auth.Auth must be passed as an ' + - 'argument to the firebase.auth.PhoneAuthProvider constructor, or the ' + - 'default firebase App instance must be initialized via ' + - 'firebase.initializeApp().'); - } - fireauth.object.setReadonlyProperties(this, { - 'providerId': fireauth.idp.ProviderId.PHONE, - 'isOAuthProvider': false - }); -}; - - -/** - * The phone info options for single-factor sign-in. Only phone number is - * required. - * @private - * @typedef {{ - * phoneNumber: string - * }} - */ -fireauth.PhoneAuthProvider.PhoneSingleFactorInfoOptions_; - -/** - * The phone info options for multi-factor enrollment. Phone number and - * multi-factor session are required. - * @private - * @typedef {{ - * phoneNumber: string, - * session: !fireauth.MultiFactorSession - * }} - */ -fireauth.PhoneAuthProvider.PhoneMultiFactorEnrollInfoOptions_; - - -/** - * The phone info options for multi-factor sign-in. Either multi-factor hint or - * multi-factor UID and multi-factor session are required. - * @private - * @typedef {{ - * multiFactorHint: !fireauth.MultiFactorInfo, - * session: !fireauth.MultiFactorSession - * }|{ - * multiFactorUid: string, - * session: !fireauth.MultiFactorSession - * }} - */ -fireauth.PhoneAuthProvider.PhoneMultiFactorSignInInfoOptions_; - - -/** - * The options for verifying the ownership of the phone number. It could be - * used for single-factor sign-in, multi-factor enrollment or multi-factor - * sign-in. - * @typedef { - * !fireauth.PhoneAuthProvider.PhoneSingleFactorInfoOptions_| - * !fireauth.PhoneAuthProvider.PhoneMultiFactorEnrollInfoOptions_| - * !fireauth.PhoneAuthProvider.PhoneMultiFactorSignInInfoOptions_ - * } - */ -fireauth.PhoneAuthProvider.PhoneInfoOptions; - - -/** - * Initiates a phone number confirmation flow. If session is provided, it is - * used to verify ownership of the second factor phone number. - * - * @param {string|!fireauth.PhoneAuthProvider.PhoneInfoOptions} phoneInfoOptions - * The user's phone options for verifying the ownship of the phone number. - * @param {!firebase.auth.ApplicationVerifier} applicationVerifier The - * application verifier for anti-abuse purposes. - * @return {!goog.Promise} A Promise that resolves with the - * verificationId of the phone number confirmation flow. - */ -fireauth.PhoneAuthProvider.prototype.verifyPhoneNumber = - function(phoneInfoOptions, applicationVerifier) { - var rpcHandler = this.auth_.getRpcHandler(); - - // Convert the promise into a goog.Promise. If the applicationVerifier throws - // an error, just propagate it to the client. Reset the reCAPTCHA widget every - // time after sending the token to the server. - return goog.Promise.resolve(applicationVerifier['verify']()) - .then(function(assertion) { - if (typeof assertion !== 'string') { - throw new fireauth.AuthError(fireauth.authenum.Error.ARGUMENT_ERROR, - 'An implementation of firebase.auth.ApplicationVerifier' + - '.prototype.verify() must return a firebase.Promise ' + - 'that resolves with a string.'); - } - - switch (applicationVerifier['type']) { - case 'recaptcha': - var session = goog.isObject(phoneInfoOptions) ? - phoneInfoOptions['session'] : null; - // PhoneInfoOptions can be a phone number string for backward - // compatibility. - var phoneNumber = goog.isObject(phoneInfoOptions) ? - phoneInfoOptions['phoneNumber'] : phoneInfoOptions; - var verifyPromise; - if (session && - session.type == fireauth.MultiFactorSession.Type.ENROLL) { - verifyPromise = session.getRawSession() - .then(function(rawSession) { - return rpcHandler.startPhoneMfaEnrollment({ - 'idToken': rawSession, - 'phoneEnrollmentInfo': { - 'phoneNumber': phoneNumber, - 'recaptchaToken': assertion - } - }); - }); - } else if (session && - session.type == - fireauth.MultiFactorSession.Type.SIGN_IN) { - verifyPromise = session.getRawSession() - .then(function(rawSession) { - var mfaEnrollmentId = - (phoneInfoOptions['multiFactorHint'] && - phoneInfoOptions['multiFactorHint']['uid']) || - phoneInfoOptions['multiFactorUid']; - return rpcHandler.startPhoneMfaSignIn({ - 'mfaPendingCredential': rawSession, - 'mfaEnrollmentId': mfaEnrollmentId, - 'phoneSignInInfo': { - 'recaptchaToken': assertion - } - }); - }); - } else { - verifyPromise = rpcHandler.sendVerificationCode({ - 'phoneNumber': phoneNumber, - 'recaptchaToken': assertion - }); - } - // Reset the applicationVerifier after code is sent. - return verifyPromise.then(function(verificationId) { - if (typeof applicationVerifier.reset === 'function') { - applicationVerifier.reset(); - } - return verificationId; - }, function(error) { - if (typeof applicationVerifier.reset === 'function') { - applicationVerifier.reset(); - } - throw error; - }); - default: - throw new fireauth.AuthError(fireauth.authenum.Error.ARGUMENT_ERROR, - 'Only firebase.auth.ApplicationVerifiers with ' + - 'type="recaptcha" are currently supported.'); - } - }); -}; - - -/** - * Creates a PhoneAuthCredential. - * @param {string} verificationId The ID of the phone number flow, to correlate - * this request with a previous call to - * PhoneAuthProvider.prototype.verifyPhoneNumber. - * @param {string} verificationCode The verification code that was sent to the - * user's phone. - * @return {!fireauth.PhoneAuthCredential} - */ -fireauth.PhoneAuthProvider.credential = - function(verificationId, verificationCode) { - if (!verificationId) { - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_SESSION_INFO); - } - if (!verificationCode) { - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE); - } - return new fireauth.PhoneAuthCredential({ - verificationId: verificationId, - verificationCode: verificationCode - }); -}; - - -// Set read only PROVIDER_ID property. -fireauth.object.setReadonlyProperties(fireauth.PhoneAuthProvider, { - 'PROVIDER_ID': fireauth.idp.ProviderId.PHONE -}); - - -// Set read only PHONE_SIGN_IN_METHOD property. -fireauth.object.setReadonlyProperties(fireauth.PhoneAuthProvider, { - 'PHONE_SIGN_IN_METHOD': fireauth.idp.SignInMethod.PHONE -}); - - -/** - * Constructs an Auth credential from a backend response. - * Note, unlike fromJSON which constructs the AuthCredential from a toJSON() - * response, this helper constructs the credential from the server response. - * @param {?Object} response The backend response to build a credential from. - * @return {?fireauth.AuthCredential} The corresponding AuthCredential. - */ -fireauth.AuthProvider.getCredentialFromResponse = function(response) { - // Handle phone Auth credential responses, as they have a different format - // from other backend responses (i.e. no providerId). - if (response['temporaryProof'] && response['phoneNumber']) { - return new fireauth.PhoneAuthCredential({ - temporaryProof: response['temporaryProof'], - phoneNumber: response['phoneNumber'] - }); - } - - // Get all OAuth response parameters from response. - var providerId = response && response['providerId']; - - // Email and password is not supported as there is no situation where the - // server would return the password to the client. - if (!providerId || providerId === fireauth.idp.ProviderId.PASSWORD) { - return null; - } - - var accessToken = response && response['oauthAccessToken']; - var accessTokenSecret = response && response['oauthTokenSecret']; - // Note this is not actually returned by the backend. It is introduced in - // rpcHandler. - var rawNonce = response && response['nonce']; - // Google Id Token returned when no additional scopes provided. - var idToken = response && response['oauthIdToken']; - // Pending token for SAML and OAuth/OIDC providers. - var pendingToken = response && response['pendingToken']; - try { - switch (providerId) { - case fireauth.idp.ProviderId.GOOGLE: - return fireauth.GoogleAuthProvider.credential( - idToken, accessToken); - - case fireauth.idp.ProviderId.FACEBOOK: - return fireauth.FacebookAuthProvider.credential( - accessToken); - - case fireauth.idp.ProviderId.GITHUB: - return fireauth.GithubAuthProvider.credential( - accessToken); - - case fireauth.idp.ProviderId.TWITTER: - return fireauth.TwitterAuthProvider.credential( - accessToken, accessTokenSecret); - - default: - if (!accessToken && !accessTokenSecret && !idToken && !pendingToken) { - return null; - } - if (pendingToken) { - if (providerId.indexOf(fireauth.constants.SAML_PREFIX) == 0) { - return new fireauth.SAMLAuthCredential(providerId, pendingToken); - } else { - // OIDC and non-default providers excluding Twitter. - return new fireauth.OAuthCredential( - providerId, - { - 'pendingToken': pendingToken, - 'idToken': response['oauthIdToken'], - 'accessToken': response['oauthAccessToken'] - }, - providerId); - } - } - return new fireauth.OAuthProvider(providerId).credential({ - 'idToken': idToken, - 'accessToken': accessToken, - 'rawNonce': rawNonce - }); - } - } catch (e) { - return null; - } -}; - - -/** - * Constructs an Auth credential from a JSON representation. - * Note, unlike getCredentialFromResponse which constructs the AuthCredential - * from a server response, this helper constructs credential from the toJSON() - * result. - * @param {!Object|string} json The JSON representation to construct credential - * from. - * @return {?fireauth.AuthCredential} The corresponding AuthCredential. - */ -fireauth.AuthProvider.getCredentialFromJSON = function(json) { - var obj = typeof json === 'string' ? JSON.parse(json) : json; - var credential; - var fromJSON = [ - fireauth.OAuthCredential.fromJSON, - fireauth.EmailAuthCredential.fromJSON, - fireauth.PhoneAuthCredential.fromJSON, - fireauth.SAMLAuthCredential.fromJSON - ]; - for (var i = 0; i < fromJSON.length; i++) { - credential = fromJSON[i](obj); - if (credential) { - return credential; - } - } - return null; -}; - - -/** - * Constructs an Auth credential from a JSON representation. - * @param {!Object|string} json The JSON representation to construct credential from. - * @return {?fireauth.AuthCredential} The corresponding AuthCredential. - */ -fireauth.AuthCredential.fromPlainObject = - fireauth.AuthProvider.getCredentialFromJSON; - - -/** - * Checks if OAuth is supported by provider, if not throws an error. - * @param {!fireauth.AuthProvider} provider The provider to check. - */ -fireauth.AuthProvider.checkIfOAuthSupported = - function(provider) { - if (!provider['isOAuthProvider']) { - throw new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - } -}; diff --git a/packages/auth/src/authevent.js b/packages/auth/src/authevent.js deleted file mode 100644 index a6e743cdfa3..00000000000 --- a/packages/auth/src/authevent.js +++ /dev/null @@ -1,210 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the Auth event object. - */ - -goog.provide('fireauth.AuthEvent'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); - - -/** - * Defines the authentication event. - * @param {!fireauth.AuthEvent.Type} type The Auth event type. - * @param {?string=} opt_eventId The event identifier. - * @param {?string=} opt_urlResponse The URL with IdP response. - * @param {?string=} opt_sessionId The session ID used to prevent session - * fixation attacks. - * @param {?fireauth.AuthError=} opt_error The optional error encountered. - * @param {?string=} opt_postBody The optional POST body. - * @param {?string=} opt_tenantId The optional tenant ID. - * @constructor - */ -fireauth.AuthEvent = function( - type, opt_eventId, opt_urlResponse, opt_sessionId, opt_error, - opt_postBody, opt_tenantId) { - /** @const @private {!fireauth.AuthEvent.Type} The Auth event type. */ - this.type_ = type; - /** @const @private {?string} The Auth event ID. */ - this.eventId_ = opt_eventId || null; - /** @const @private {?string} The callback URL with the sign in response. */ - this.urlResponse_ = opt_urlResponse || null; - /** @const @private {?string} The sign in operation session ID. */ - this.sessionId_ = opt_sessionId || null; - /** @const @private {?string} The POST body string if available. */ - this.postBody_ = opt_postBody || null; - /** @const @private {?string} The tenant ID if available. */ - this.tenantId_ = opt_tenantId || null; - /** - * @const @private {?fireauth.AuthError} The Auth event error if available. - */ - this.error_ = opt_error || null; - if (!this.urlResponse_ && !this.error_) { - // Either URL or error is required. They can't be both null. - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT); - } else if (this.urlResponse_ && this.error_) { - // An error must not be provided when a URL is available. - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT); - } else if (this.urlResponse_ && !this.sessionId_) { - // A session ID must accompany a URL response. - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT); - } -}; - - - -/** - * Auth event operation types. - * All Auth event types that are used for popup operations should be suffixed - * with `Popup`, whereas those used for redirect operations should be suffixed - * with `Redirect`. - * TODO: consider changing the type from a string to an object with ID - * and some metadata for determining mode: redirect, popup or none. - * @enum {string} - */ -fireauth.AuthEvent.Type = { - LINK_VIA_POPUP: 'linkViaPopup', - LINK_VIA_REDIRECT: 'linkViaRedirect', - REAUTH_VIA_POPUP: 'reauthViaPopup', - REAUTH_VIA_REDIRECT: 'reauthViaRedirect', - SIGN_IN_VIA_POPUP: 'signInViaPopup', - SIGN_IN_VIA_REDIRECT: 'signInViaRedirect', - UNKNOWN: 'unknown', - VERIFY_APP: 'verifyApp' -}; - - -/** - * @param {!fireauth.AuthEvent} event The Auth event. - * @return {boolean} Whether the event is a redirect type. - */ -fireauth.AuthEvent.isRedirect = function(event) { - return !!event.getType().match(/Redirect$/); -}; - - -/** - * @param {!fireauth.AuthEvent} event The Auth event. - * @return {boolean} Whether the event is a popup type. - */ -fireauth.AuthEvent.isPopup = function(event) { - return !!event.getType().match(/Popup$/); -}; - - -/** @return {!fireauth.AuthEvent.Type} The type of Auth event. */ -fireauth.AuthEvent.prototype.getType = function() { - return this.type_; -}; - - -/** @return {?string} The Auth event identifier. */ -fireauth.AuthEvent.prototype.getEventId = function() { - return this.eventId_; -}; - - -/** @return {string} The event unique identifier. */ -fireauth.AuthEvent.prototype.getUid = function() { - var components = []; - components.push(this.type_); - if (this.eventId_) { - components.push(this.eventId_); - } - if (this.sessionId_) { - components.push(this.sessionId_); - } - if (this.tenantId_) { - components.push(this.tenantId_); - } - return components.join('-'); -}; - - -/** @return {?string} The url response of Auth event. */ -fireauth.AuthEvent.prototype.getUrlResponse = function() { - return this.urlResponse_; -}; - - -/** @return {?string} The session ID Auth event. */ -fireauth.AuthEvent.prototype.getSessionId = function() { - return this.sessionId_; -}; - - -/** @return {?string} The POST body of the Auth event, if available. */ -fireauth.AuthEvent.prototype.getPostBody = function() { - return this.postBody_; -}; - - -/** @return {?string} The tenant ID of the Auth event, if available. */ -fireauth.AuthEvent.prototype.getTenantId = function() { - return this.tenantId_; -}; - - -/** @return {?fireauth.AuthError} The error of Auth event. */ -fireauth.AuthEvent.prototype.getError = function() { - return this.error_; -}; - - -/** @return {boolean} Whether Auth event has an error. */ -fireauth.AuthEvent.prototype.hasError = function() { - return !!this.error_; -}; - - -/** @return {!Object} The plain object representation of event. */ -fireauth.AuthEvent.prototype.toPlainObject = function() { - return { - 'type': this.type_, - 'eventId': this.eventId_, - 'urlResponse': this.urlResponse_, - 'sessionId': this.sessionId_, - 'postBody': this.postBody_, - 'tenantId': this.tenantId_, - 'error': this.error_ && this.error_.toPlainObject() - }; -}; - - -/** - * @param {?Object} rawResponse The plain object representation of Auth event. - * @return {?fireauth.AuthEvent} The Auth event representation of plain object. - */ -fireauth.AuthEvent.fromPlainObject = function(rawResponse) { - var response = rawResponse || {}; - if (response['type']) { - return new fireauth.AuthEvent( - response['type'], - response['eventId'], - response['urlResponse'], - response['sessionId'], - response['error'] && - fireauth.AuthError.fromPlainObject(response['error']), - response['postBody'], - response['tenantId'] - ); - } - return null; -}; diff --git a/packages/auth/src/autheventmanager.js b/packages/auth/src/autheventmanager.js deleted file mode 100644 index c7aeebe32c8..00000000000 --- a/packages/auth/src/autheventmanager.js +++ /dev/null @@ -1,1229 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the Auth event manager instance. - */ - -goog.provide('fireauth.AuthEventHandler'); -goog.provide('fireauth.AuthEventManager'); -goog.provide('fireauth.AuthEventManager.Result'); -goog.provide('fireauth.PopupAuthEventProcessor'); -goog.provide('fireauth.RedirectAuthEventProcessor'); - -goog.require('fireauth.AuthCredential'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.CordovaHandler'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.storage.PendingRedirectManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Timer'); -goog.require('goog.array'); - - -/** - * Initializes the Auth event manager which provides the mechanism to connect - * external Auth events to their corresponding listeners. - * @param {string} authDomain The Firebase authDomain used to determine the - * OAuth helper page domain. - * @param {string} apiKey The API key for sending backend Auth requests. - * @param {string} appName The App ID for the Auth instance that triggered this - * request. - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @constructor - */ -fireauth.AuthEventManager = function(authDomain, apiKey, appName, emulatorConfig) { - /** - * @private {!Object} The map of processed auth event IDs. - */ - this.processedEvents_ = {}; - /** @private {number} The last saved processed event time in milliseconds. */ - this.lastProcessedEventTime_ = 0; - /** @private {string} The Auth domain. */ - this.authDomain_ = authDomain; - /** @private {string} The browser API key. */ - this.apiKey_ = apiKey; - /** @private {string} The App name. */ - this.appName_ = appName; - /** @private @const {?fireauth.constants.EmulatorSettings|undefined} The emulator config. */ - this.emulatorConfig_ = emulatorConfig; - /** - * @private {!Array} List of subscribed handlers. - */ - this.subscribedHandlers_ = []; - /** - * @private {boolean} Whether the Auth event manager instance is initialized. - */ - this.initialized_ = false; - /** @private {function(?fireauth.AuthEvent)} The Auth event handler. */ - this.authEventHandler_ = goog.bind(this.handleAuthEvent_, this); - /** @private {!fireauth.RedirectAuthEventProcessor} The redirect event - * processor. */ - this.redirectAuthEventProcessor_ = - new fireauth.RedirectAuthEventProcessor(this); - /** @private {!fireauth.PopupAuthEventProcessor} The popup event processor. */ - this.popupAuthEventProcessor_ = new fireauth.PopupAuthEventProcessor(this); - /** - * @private {!fireauth.storage.PendingRedirectManager} The pending redirect - * storage manager instance. - */ - this.pendingRedirectStorageManager_ = - new fireauth.storage.PendingRedirectManager( - fireauth.AuthEventManager.getKey_(this.apiKey_, this.appName_)); - - /** - * @private {!Object.} - * Map containing Firebase event processor instances keyed by event type. - */ - this.typeToManager_ = {}; - this.typeToManager_[fireauth.AuthEvent.Type.UNKNOWN] = - this.redirectAuthEventProcessor_; - this.typeToManager_[fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT] = - this.redirectAuthEventProcessor_; - this.typeToManager_[fireauth.AuthEvent.Type.LINK_VIA_REDIRECT] = - this.redirectAuthEventProcessor_; - this.typeToManager_[fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT] = - this.redirectAuthEventProcessor_; - this.typeToManager_[fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP] = - this.popupAuthEventProcessor_; - this.typeToManager_[fireauth.AuthEvent.Type.LINK_VIA_POPUP] = - this.popupAuthEventProcessor_; - this.typeToManager_[fireauth.AuthEvent.Type.REAUTH_VIA_POPUP] = - this.popupAuthEventProcessor_; - /** - * @private {!fireauth.OAuthSignInHandler} The OAuth sign in handler depending - * on the current environment. - */ - this.oauthSignInHandler_ = - fireauth.AuthEventManager.instantiateOAuthSignInHandler( - this.authDomain_, - this.apiKey_, - this.appName_, - firebase.SDK_VERSION || null, - fireauth.constants.clientEndpoint, - this.emulatorConfig_); -}; - - -/** - * @const {number} The number of milliseconds since the last processed - * event before the event duplication cache is cleared. This is currently - * 10 minutes. - */ -fireauth.AuthEventManager.EVENT_DUPLICATION_CACHE_DURATION = 10 * 60 * 1000; - - -/** - * @return {!fireauth.RedirectAuthEventProcessor} The redirect event processor. - */ -fireauth.AuthEventManager.prototype.getRedirectAuthEventProcessor = function() { - return this.redirectAuthEventProcessor_; -}; - - -/** @return {!fireauth.PopupAuthEventProcessor} The popup event processor. */ -fireauth.AuthEventManager.prototype.getPopupAuthEventProcessor = function() { - return this.popupAuthEventProcessor_; -}; - - -/** - * Instantiates an OAuth sign-in handler depending on the current environment - * and returns it. - * @param {string} authDomain The Firebase authDomain used to determine the - * OAuth helper page domain. - * @param {string} apiKey The API key for sending backend Auth requests. - * @param {string} appName The App ID for the Auth instance that triggered this - * request. - * @param {?string} version The SDK client version. - * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @return {!fireauth.OAuthSignInHandler} The OAuth sign in handler depending - * on the current environment. - */ -fireauth.AuthEventManager.instantiateOAuthSignInHandler = - function(authDomain, apiKey, appName, version, opt_endpointId, emulatorConfig) { - // This assumes that android/iOS file environment must be a Cordova - // environment which is not true. This is the best way currently available - // to instantiate this synchronously without waiting for checkIfCordova to - // resolve. If it is determined that the Cordova was falsely detected, it will - // be caught via actionable public popup and redirect methods. - return fireauth.util.isAndroidOrIosCordovaScheme() ? - new fireauth.CordovaHandler( - authDomain, - apiKey, - appName, - version, - undefined, - undefined, - opt_endpointId, - emulatorConfig) : - new fireauth.iframeclient.IfcHandler( - authDomain, - apiKey, - appName, - version, - opt_endpointId, - emulatorConfig); -}; - - -/** Reset iframe. This will require reinitializing it.*/ -fireauth.AuthEventManager.prototype.reset = function() { - // Reset initialized status. This will force a popup request to re-initialize - // the iframe. - this.initialized_ = false; - // Remove any previous existing Auth event listener. - this.oauthSignInHandler_.removeAuthEventListener(this.authEventHandler_); - // Construct a new instance of OAuth sign in handler. - - this.oauthSignInHandler_ = - fireauth.AuthEventManager.instantiateOAuthSignInHandler( - this.authDomain_, - this.apiKey_, - this.appName_, - firebase.SDK_VERSION || null, - null, - this.emulatorConfig_); - this.processedEvents_ = {}; -}; - - -/** - * Clears the cached redirect result as long as there is no pending redirect - * result being processed. Unrecoverable errors will not be cleared. - */ -fireauth.AuthEventManager.prototype.clearRedirectResult = function() { - this.redirectAuthEventProcessor_.clearRedirectResult(); -}; - - -/** - * @typedef {{ - * user: (?fireauth.AuthUser|undefined), - * credential: (?fireauth.AuthCredential|undefined), - * operationType: (?string|undefined), - * additionalUserInfo: (?fireauth.AdditionalUserInfo|undefined) - * }} - */ -fireauth.AuthEventManager.Result; - - -/** - * Whether to enable Auth event manager subscription. - * @const {boolean} - */ -fireauth.AuthEventManager.ENABLED = true; - - -/** - * Initializes the ifchandler and add Auth event listener on it. - * @return {!goog.Promise} The promise that resolves when the iframe is ready. - */ -fireauth.AuthEventManager.prototype.initialize = function() { - var self = this; - // Initialize once. - if (!this.initialized_) { - this.initialized_ = true; - // Listen to Auth events on iframe. - this.oauthSignInHandler_.addAuthEventListener(this.authEventHandler_); - } - var previousOauthSignInHandler = this.oauthSignInHandler_; - // This should initialize ifchandler underneath. - // Return on OAuth handler ready promise. - // Check for error in ifcHandler used to embed the iframe. - return this.oauthSignInHandler_.initializeAndWait() - .thenCatch(function(error) { - // Force ifchandler to reinitialize on retrial. - if (self.oauthSignInHandler_ == previousOauthSignInHandler) { - // If a new OAuth sign in handler was already created, do not reset. - self.reset(); - } - throw error; - }); -}; - - -/** - * Called after it is determined that there is no pending redirect result. - * Will populate the redirect result if it is guaranteed to be null and will - * force an early initialization of the OAuth sign in handler if the - * environment requires it. - * @private - */ -fireauth.AuthEventManager.prototype.initializeWithNoPendingRedirectResult_ = - function() { - var self = this; - // Check if the OAuth sign in handler should be initialized early in all - // cases. - if (this.oauthSignInHandler_.shouldBeInitializedEarly()) { - this.initialize().thenCatch(function(error) { - // Current environment was falsely detected as Cordova, trigger a fake - // Auth event to notify getRedirectResult that operation is not supported. - var notSupportedEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - if (fireauth.AuthEventManager.isCordovaFalsePositive_( - /** @type {?fireauth.AuthError} */ (error))) { - self.handleAuthEvent_(notSupportedEvent); - } - }); - } - // For environments where storage is volatile, we can't determine that - // there is no pending redirect response. This is true in Cordova - // where an activity would be destroyed in some cases and the - // sessionStorage is lost. - if (!this.oauthSignInHandler_.hasVolatileStorage()) { - // Since there is no redirect result, it is safe to default to empty - // redirect result instead of blocking on this. - // The downside here is that on iOS devices, calling signInWithPopup - // after getRedirectResult resolves and the iframe does not finish - // loading, the popup event propagating to the iframe would not be - // detected. This is because in iOS devices, storage events only trigger - // in iframes but are not actually saved in web storage. The iframe must - // be embedded and ready before the storage event propagates. Otherwise - // it won't be detected. - this.redirectAuthEventProcessor_.defaultToEmptyResponse(); - } -}; - - -/** - * Subscribes an Auth event handler to list of handlers. - * @param {!fireauth.AuthEventHandler} handler The instance to subscribe. - */ -fireauth.AuthEventManager.prototype.subscribe = function(handler) { - if (!goog.array.contains(this.subscribedHandlers_, handler)) { - this.subscribedHandlers_.push(handler); - } - if (this.initialized_) { - return; - } - var self = this; - // Check pending redirect status. - this.pendingRedirectStorageManager_.getPendingStatus() - .then(function(status) { - // Pending redirect detected. - if (status) { - // Remove pending status and initialize. - self.pendingRedirectStorageManager_.removePendingStatus() - .then(function() { - self.initialize().thenCatch(function(error) { - // Current environment was falsely detected as Cordova, trigger a - // fake Auth event to notify getRedirectResult that operation is - // not supported. - var notSupportedEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - if (fireauth.AuthEventManager.isCordovaFalsePositive_( - /** @type {?fireauth.AuthError} */ (error))) { - self.handleAuthEvent_(notSupportedEvent); - } - }); - }); - } else { - // No previous redirect, default to empty response. - self.initializeWithNoPendingRedirectResult_(); - } - }).thenCatch(function(error) { - // Error checking pending status, default to empty response. - self.initializeWithNoPendingRedirectResult_(); - }); -}; - - -/** - * @param {!fireauth.AuthEventHandler} handler The possible subscriber. - * @return {boolean} Whether the handle is subscribed. - */ -fireauth.AuthEventManager.prototype.isSubscribed = function(handler) { - return goog.array.contains(this.subscribedHandlers_, handler); -}; - - -/** - * Unsubscribes an Auth event handler to list of handlers. - * @param {!fireauth.AuthEventHandler} handler The instance to unsubscribe. - */ -fireauth.AuthEventManager.prototype.unsubscribe = function(handler) { - goog.array.removeAllIf(this.subscribedHandlers_, function(ele) { - return ele == handler; - }); -}; - - -/** - * @param {?fireauth.AuthEvent} authEvent External Auth event to check. - * @return {boolean} Whether the event was previously processed. - * @private - */ -fireauth.AuthEventManager.prototype.hasProcessedAuthEvent_ = - function(authEvent) { - // Prevent duplicate event tracker from growing too large. - if (Date.now() - this.lastProcessedEventTime_ >= - fireauth.AuthEventManager.EVENT_DUPLICATION_CACHE_DURATION) { - this.processedEvents_ = {}; - this.lastProcessedEventTime_ = 0; - } - if (authEvent && authEvent.getUid() && - this.processedEvents_.hasOwnProperty(authEvent.getUid())) { - // If event is already processed, ignore it. - return true; - } - return false; -}; - - -/** - * Saves the provided event uid to prevent processing duplication. - * @param {?fireauth.AuthEvent} authEvent External Auth event to track in - * processed list of events. - * @private - */ -fireauth.AuthEventManager.prototype.saveProcessedAuthEvent_ = - function(authEvent) { - if (authEvent && - (authEvent.getSessionId() || authEvent.getEventId())) { - // Save processed event ID. We keep the cache for 10 minutes to prevent it - // from growing too large. - this.processedEvents_[ - /** @type {string} */ (authEvent.getUid())] = true; - // Save last processing time. - this.lastProcessedEventTime_ = Date.now(); - } -}; - - -/** - * Handles external Auth event detected by the OAuth sign-in handler. - * @param {?fireauth.AuthEvent} authEvent External Auth event detected by - * iframe. - * @return {boolean} Whether the event found an appropriate owner that can - * handle it. This signals to the OAuth helper iframe that the event is safe - * to delete. - * @private - */ -fireauth.AuthEventManager.prototype.handleAuthEvent_ = function(authEvent) { - // This should not happen as fireauth.iframe.AuthRelay will not send null - // events. - if (!authEvent) { - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT); - } - if (this.hasProcessedAuthEvent_(authEvent)) { - // If event is already processed, ignore it. - return false; - } - // Initialize event processed status to false. When set to false, the event is - // not clear to delete in the OAuth helper iframe as the owner of this event - // could be a user in another tab. - var processed = false; - // Lookup a potential handler for this event. - for (var i = 0; i < this.subscribedHandlers_.length; i++) { - var potentialHandler = this.subscribedHandlers_[i]; - if (potentialHandler.canHandleAuthEvent( - authEvent.getType(), authEvent.getEventId())) { - var eventManager = this.typeToManager_[authEvent.getType()]; - if (eventManager) { - eventManager.processAuthEvent(authEvent, potentialHandler); - // Prevent events with event IDs or session IDs from duplicate - // processing. - this.saveProcessedAuthEvent_(authEvent); - } - // Event has been processed, free to clear in OAuth helper. - processed = true; - break; - } - } - // If no redirect response ready yet, default to an empty response. - this.redirectAuthEventProcessor_.defaultToEmptyResponse(); - // Notify iframe of processed status. - return processed; -}; - - -/** - * The popup promise timeout delay with units in ms between the time the iframe - * is ready (successfully embedded on the page) and the time the popup Auth - * event is detected in the parent container. - * @const {!fireauth.util.Delay} - * @private - */ -fireauth.AuthEventManager.POPUP_TIMEOUT_MS_ = - new fireauth.util.Delay(2000, 10000); - - -/** - * The redirect promise timeout delay with units in ms. Unlike the popup - * timeout, this covers the entire duration from start to getRedirectResult - * resolution. - * @const {!fireauth.util.Delay} - * @private - */ -fireauth.AuthEventManager.REDIRECT_TIMEOUT_MS_ = - new fireauth.util.Delay(30000, 60000); - - -/** - * Returns the redirect result. If coming back from a successful redirect sign - * in, will resolve to the signed in user. If coming back from an unsuccessful - * redirect sign, will reject with the proper error. If no redirect operation - * called, resolves with null. - * @return {!goog.Promise} - */ -fireauth.AuthEventManager.prototype.getRedirectResult = function() { - return this.redirectAuthEventProcessor_.getRedirectResult(); -}; - - -/** - * Processes the popup request. The popup instance must be provided externally - * and on error, the requestor must close the window. - * @param {?Window} popupWin The popup window reference. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {string=} opt_eventId The optional event ID. - * @param {boolean=} opt_alreadyRedirected Whether popup is already redirected - * to final destination. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} The popup window promise. - */ -fireauth.AuthEventManager.prototype.processPopup = - function(popupWin, mode, provider, opt_eventId, opt_alreadyRedirected, - opt_tenantId) { - var self = this; - return this.oauthSignInHandler_.processPopup( - popupWin, - mode, - provider, - // On initialization, add Auth event listener if not already added. - function() { - if (!self.initialized_) { - self.initialized_ = true; - // Listen to Auth events on iframe. - self.oauthSignInHandler_.addAuthEventListener(self.authEventHandler_); - } - }, - // On error, reset to force re-initialization on retrial. - function(error) { - self.reset(); - }, - opt_eventId, - opt_alreadyRedirected, - opt_tenantId); -}; - - -/** - * @param {?fireauth.AuthError} error The error to check for Cordova false - * positive. - * @return {boolean} Whether the current environment was falsely identified as - * Cordova. - * @private - */ -fireauth.AuthEventManager.isCordovaFalsePositive_ = function(error) { - if (error && error['code'] == 'auth/cordova-not-ready') { - return true; - } - return false; -}; - - -/** - * Processes the redirect request. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {string=} opt_eventId The optional event ID. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} - */ -fireauth.AuthEventManager.prototype.processRedirect = - function(mode, provider, opt_eventId, opt_tenantId) { - var self = this; - var error; - // Save pending status first. - return this.pendingRedirectStorageManager_.setPendingStatus() - .then(function() { - // Try to redirect. - return self.oauthSignInHandler_.processRedirect( - mode, provider, opt_eventId, opt_tenantId) - .thenCatch(function(e) { - if (fireauth.AuthEventManager.isCordovaFalsePositive_( - /** @type {?fireauth.AuthError} */ (e))) { - throw new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - } - // On failure, remove pending status and rethrow the error. - error = e; - return self.pendingRedirectStorageManager_.removePendingStatus() - .then(function() { - throw error; - }); - }) - .then(function() { - // Resolve, if the OAuth handler unloads the page on redirect. - if (!self.oauthSignInHandler_.unloadsOnRedirect()) { - // Relevant to Cordova case, will not matter in web case where - // browser redirects. - // In Cordova, the activity could still be running in the background - // so we need to wait for getRedirectResult to resolve before - // resolving this current promise. - // Otherwise, if the activity is destroyed, getRedirectResult would - // be used. - // At this point, authEvent should have been triggered. - // When this promise resolves, the developer should be able to - // call getRedirectResult to get the result of this operation. - // Remove pending status as result should be resolved. - return self.pendingRedirectStorageManager_.removePendingStatus() - .then(function() { - // Ensure redirect result ready before resolving. - return self.getRedirectResult(); - }).then(function(result) { - // Do nothing. Developer expected to call getRedirectResult to - // get result. - }).thenCatch(function(error) { - // Do nothing. Developer expected to call getRedirectResult to - // get result. - }); - } else { - // For environments that will unload the page on redirect, keep - // the promise pending on success. This makes it easier to reuse - // the same code for Cordova environment and browser environment. - // The developer can always add getRedirectResult on promise - // resolution and expect that when it runs, the redirect operation - // was completed. - return new goog.Promise(function(resolve, reject) { - // Keep this pending. - }); - } - }); - }); -}; - - -/** - * Waits for popup window to close. When closed start timeout listener for popup - * pending promise. If in the process, it was detected that the iframe does not - * support web storage, the popup is closed and the web storage unsupported - * error is thrown. - * @param {!fireauth.AuthEventHandler} owner The owner of the event. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!Window} popupWin The popup window. - * @param {?string=} opt_eventId The event ID. - * @return {!goog.Promise} - */ -fireauth.AuthEventManager.prototype.startPopupTimeout = - function(owner, mode, popupWin, opt_eventId) { - return this.oauthSignInHandler_.startPopupTimeout( - popupWin, - // On popup error such as popup closed by user or web storage not - // supported. - function(error) { - // Notify owner of the error. - owner.resolvePendingPopupEvent(mode, null, error, opt_eventId); - }, - fireauth.AuthEventManager.POPUP_TIMEOUT_MS_.get()); -}; - - - -/** - * @private {!Object.} Map containing - * Firebase event manager instances keyed by Auth event manager ID. - */ -fireauth.AuthEventManager.manager_ = {}; - - -/** - * The separator for manager keys to concatenate app name and apiKey. - * @const {string} - * @private - */ -fireauth.AuthEventManager.KEY_SEPARATOR_ = ':'; - - -/** - * @param {string} apiKey The API key for sending backend Auth requests. - * @param {string} appName The Auth instance that initiated the Auth event. - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @return {string} The key identifying the Auth event manager instance. - * @private - */ -fireauth.AuthEventManager.getKey_ = function(apiKey, appName, emulatorConfig) { - var key = apiKey + fireauth.AuthEventManager.KEY_SEPARATOR_ + appName; - if (emulatorConfig) { - key = key + fireauth.AuthEventManager.KEY_SEPARATOR_ + emulatorConfig.url; - } - return key; -} - - -/** - * @param {string} authDomain The Firebase authDomain used to determine the - * OAuth helper page domain. - * @param {string} apiKey The API key for sending backend Auth requests. - * @param {string} appName The Auth instance that initiated the Auth event - * manager. - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @return {!fireauth.AuthEventManager} the requested manager instance. - */ -fireauth.AuthEventManager.getManager = function (authDomain, apiKey, appName, emulatorConfig) { - // Construct storage key. - var key = fireauth.AuthEventManager.getKey_( - apiKey, - appName, - emulatorConfig - ); - if (!fireauth.AuthEventManager.manager_[key]) { - fireauth.AuthEventManager.manager_[key] = - new fireauth.AuthEventManager( - authDomain, - apiKey, - appName, - emulatorConfig - ); - } - return fireauth.AuthEventManager.manager_[key]; -}; - - -/** - * The interface that represents a specific type of Auth event processor. - * @interface - */ -fireauth.AuthEventProcessor = function() {}; - - -/** - * Completes the processing of an external Auth event detected by the embedded - * iframe. - * @param {?fireauth.AuthEvent} authEvent External Auth event detected by - * iframe. - * @param {!fireauth.AuthEventHandler} owner The owner of the event. - * @return {!goog.Promise} - */ -fireauth.AuthEventProcessor.prototype.processAuthEvent = - function(authEvent, owner) {}; - - - -/** - * Redirect Auth event manager. - * @param {!fireauth.AuthEventManager} manager The parent Auth event manager. - * @constructor - * @implements {fireauth.AuthEventProcessor} - */ -fireauth.RedirectAuthEventProcessor = function(manager) { - this.manager_ = manager; - // Only one redirect result can be tracked on first load. - /** - * @private {?function():!goog.Promise} - * Redirect result resolver. This will be used to resolve the - * getRedirectResult promise. When the redirect result is obtained, this - * field will be set. - */ - this.redirectedUserPromise_ = null; - /** - * @private {!Array} Pending - * promise redirect resolver. When the redirect result is obtained and the - * user is detected, this will be called. - */ - this.redirectResolve_ = []; - /** - * @private {!Array} Pending Promise redirect rejecter. When the - * redirect result is obtained and an error is detected, this will be - * called. - */ - this.redirectReject_ = []; - /** @private {?goog.Promise} Pending timeout promise for redirect. */ - this.redirectTimeoutPromise_ = null; - /** - * @private {boolean} Whether redirect result is resolved. This is true - * when a valid Auth event has been triggered. - */ - this.redirectResultResolved_ = false; - /** - * @private {boolean} Whether an unrecoverable error was detected. This - * includes web storage unsupported or operation not allowed errors. - */ - this.unrecoverableErrorDetected_ = false; -}; - - -/** Reset any previous redirect result. */ -fireauth.RedirectAuthEventProcessor.prototype.reset = function() { - // Reset to allow override getRedirectResult. This is relevant for Cordova - // environment where redirect events do not necessarily unload the current - // page. - this.redirectedUserPromise_ = null; - if (this.redirectTimeoutPromise_) { - this.redirectTimeoutPromise_.cancel(); - this.redirectTimeoutPromise_ = null; - } -}; - - -/** - * Completes the processing of an external Auth event detected by the embedded - * iframe. - * @param {?fireauth.AuthEvent} authEvent External Auth event detected by - * iframe. - * @param {!fireauth.AuthEventHandler} owner The owner of the event. - * @return {!goog.Promise} - * @override - */ -fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent = - function(authEvent, owner) { - // This should not happen as fireauth.iframe.AuthRelay will not send null - // events. - if (!authEvent) { - return goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT)); - } - // Reset any pending redirect result. This event will overwrite it. - this.reset(); - this.redirectResultResolved_ = true; - var mode = authEvent.getType(); - var eventId = authEvent.getEventId(); - // Check if web storage is not supported in the iframe. - var isWebStorageNotSupported = - authEvent.getError() && - authEvent.getError()['code'] == 'auth/web-storage-unsupported'; - /// Check if operation is supported in this environment. - var isOperationNotSupported = - authEvent.getError() && - authEvent.getError()['code'] == 'auth/operation-not-supported-in-this-' + - 'environment'; - this.unrecoverableErrorDetected_ = - !!(isWebStorageNotSupported || isOperationNotSupported); - // UNKNOWN mode is always triggered on load by iframe when no popup/redirect - // data is available. If web storage unsupported error is thrown, process as - // error and not as unknown event. If the operation is not supported in this - // environment, also treat as an error and not as an unknown event. - if (mode == fireauth.AuthEvent.Type.UNKNOWN && - !isWebStorageNotSupported && - !isOperationNotSupported) { - return this.processUnknownEvent_(); - } else if (authEvent.hasError()) { - return this.processErrorEvent_(authEvent, owner); - } else if (owner.getAuthEventHandlerFinisher(mode, eventId)) { - return this.processSuccessEvent_(authEvent, owner); - } else { - return goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT)); - } -}; - - -/** - * Sets an empty redirect result response when no redirect result is available. - */ -fireauth.RedirectAuthEventProcessor.prototype.defaultToEmptyResponse = - function() { - // If the first event does not resolve redirectResult and no subscriber can - // handle it, set redirect result to null. - // An example of this scenario would be a link via redirect that was triggered - // by a user that was not logged in. canHandleAuthEvent will be false for all - // subscribers. So make sure getRedirectResult when called will resolve to a - // null user. - if (!this.redirectResultResolved_) { - this.redirectResultResolved_ = true; - // No Auth event available, getRedirectResult should resolve with null. - this.setRedirectResult_(false, null, null); - } -}; - - -/** - * Clears the cached redirect result as long as there is no pending redirect - * result being processed. Unrecoverable errors will not be cleared. - */ -fireauth.RedirectAuthEventProcessor.prototype.clearRedirectResult = function() { - // Clear the result if it is already resolved and no unrecoverable errors are - // detected. - if (this.redirectResultResolved_ && !this.unrecoverableErrorDetected_) { - this.setRedirectResult_(false, null, null); - } -}; - - -/** - * Processes the unknown event. - * @return {!goog.Promise} - * @private - */ -fireauth.RedirectAuthEventProcessor.prototype.processUnknownEvent_ = - function() { - // No Auth event available, getRedirectResult should resolve with null. - this.setRedirectResult_(false, null, null); - return goog.Promise.resolve(); -}; - - -/** - * Processes an error event. - * @param {?fireauth.AuthEvent} authEvent External Auth event detected by - * iframe. - * @param {!fireauth.AuthEventHandler} owner The owner of the event. - * @return {!goog.Promise} - * @private - */ -fireauth.RedirectAuthEventProcessor.prototype.processErrorEvent_ = - function(authEvent, owner) { - // Set redirect result to resolve with null if event is not a redirect or - // reject with error if event is an error. - this.setRedirectResult_(true, null, authEvent.getError()); - return goog.Promise.resolve(); -}; - - -/** - * Processes a successful event. - * @param {?fireauth.AuthEvent} authEvent External Auth event detected by - * iframe. - * @param {!fireauth.AuthEventHandler} owner The owner of the event. - * @return {!goog.Promise} - * @private - */ -fireauth.RedirectAuthEventProcessor.prototype.processSuccessEvent_ = - function(authEvent, owner) { - var self = this; - var eventId = authEvent.getEventId(); - var mode = authEvent.getType(); - var handler = owner.getAuthEventHandlerFinisher(mode, eventId); - var requestUri = /** @type {string} */ (authEvent.getUrlResponse()); - var sessionId = /** @type {string} */ (authEvent.getSessionId()); - var postBody = /** @type {?string} */ (authEvent.getPostBody()); - var tenantId = /** @type {?string} */ (authEvent.getTenantId()); - var isRedirect = fireauth.AuthEvent.isRedirect(authEvent); - // Complete sign in or link account operation and then pass result to - // relevant pending popup promise. - return handler(requestUri, sessionId, tenantId, postBody) - .then(function(popupRedirectResponse) { - // Flow completed. - // For a redirect operation resolve with the popupRedirectResponse, - // otherwise resolve with null. - self.setRedirectResult_(isRedirect, popupRedirectResponse, null); - }).thenCatch(function(error) { - // Flow not completed due to error. - // For a redirect operation reject with the error, otherwise resolve - // with null. - self.setRedirectResult_( - isRedirect, null, /** @type {!fireauth.AuthError} */ (error)); - // Always resolve. - return; - }); -}; - - -/** - * Sets redirect error result. - * @param {!fireauth.AuthError} error The redirect operation error. - * @private - */ -fireauth.RedirectAuthEventProcessor.prototype.setRedirectReject_ = - function(error) { - // If a redirect error detected, reject getRedirectResult with that error. - this.redirectedUserPromise_ = function() { - return goog.Promise.reject(error); - }; - // Reject all pending getRedirectResult promises. - if (this.redirectReject_.length) { - for (var i = 0; i < this.redirectReject_.length; i++) { - this.redirectReject_[i](error); - } - } -}; - - -/** - * Sets redirect success result. - * @param {!fireauth.AuthEventManager.Result} popupRedirectResult The - * resolved user for a successful or null user redirect. - * @private - */ -fireauth.RedirectAuthEventProcessor.prototype.setRedirectResolve_ = - function(popupRedirectResult) { - // If a redirect user detected, resolve getRedirectResult with the - // popupRedirectResult. - // Result should not be null in this case. - this.redirectedUserPromise_ = function() { - return goog.Promise.resolve( - /** @type {!fireauth.AuthEventManager.Result} */ (popupRedirectResult)); - }; - // Resolve all pending getRedirectResult promises. - if (this.redirectResolve_.length) { - for (var i = 0; i < this.redirectResolve_.length; i++) { - this.redirectResolve_[i]( - /** @type {!fireauth.AuthEventManager.Result} */ ( - popupRedirectResult)); - } - } -}; - - -/** - * @param {boolean} isRedirect Whether Auth event is a redirect event. - * @param {?fireauth.AuthEventManager.Result} popupRedirectResult The - * resolved user for a successful redirect. This user is null if no redirect - * operation run. - * @param {?fireauth.AuthError} error The redirect operation error. - * @private - */ -fireauth.RedirectAuthEventProcessor.prototype.setRedirectResult_ = - function(isRedirect, popupRedirectResult, error) { - if (isRedirect) { - // This is a redirect operation, either resolves with user or error. - if (error) { - // If a redirect error detected, reject getRedirectResult with that error. - this.setRedirectReject_(error); - } else { - // If a redirect user detected, resolve getRedirectResult with the - // popupRedirectResult. - // Result should not be null in this case. - this.setRedirectResolve_( - /** @type {!fireauth.AuthEventManager.Result} */ ( - popupRedirectResult)); - } - } else { - // Not a redirect, set redirectUser_ to return null. - this.setRedirectResolve_({ - 'user': null - }); - } - // Reset all pending promises. - this.redirectResolve_ = []; - this.redirectReject_ = []; -}; - - -/** - * Returns the redirect result. If coming back from a successful redirect sign - * in, will resolve to the signed in user. If coming back from an unsuccessful - * redirect sign, will reject with the proper error. If no redirect operation - * called, resolves with null. - * @return {!goog.Promise} - */ -fireauth.RedirectAuthEventProcessor.prototype.getRedirectResult = function() { - var self = this; - // Initial result could be overridden in the case of Cordova. - // Auth domain must be included for this to resolve. - // If still pending just return the pending promise. - var p = new goog.Promise(function(resolve, reject) { - // The following logic works if this method was called before Auth event - // is triggered. - if (!self.redirectedUserPromise_) { - // Save resolves and rejects of pending promise for redirect operation. - self.redirectResolve_.push(resolve); - self.redirectReject_.push(reject); - // Start timeout listener to getRedirectResult pending promise. - // Call this only when redirectedUserPromise_ is not determined. - self.startRedirectTimeout_(); - } else { - // Called after Auth event is triggered. - self.redirectedUserPromise_().then(resolve, reject); - } - }); - return /** @type {!goog.Promise} */ (p); -}; - - -/** - * Starts timeout listener for getRedirectResult pending promise. This method - * should not be called again after getRedirectResult's redirectedUserPromise_ - * is determined. - * @private - */ -fireauth.RedirectAuthEventProcessor.prototype.startRedirectTimeout_ = - function() { - // Expire pending timeout promise for popup operation. - var self = this; - var error = new fireauth.AuthError( - fireauth.authenum.Error.TIMEOUT); - if (this.redirectTimeoutPromise_) { - this.redirectTimeoutPromise_.cancel(); - } - // For redirect mode. - this.redirectTimeoutPromise_ = - goog.Timer.promise(fireauth.AuthEventManager.REDIRECT_TIMEOUT_MS_.get()) - .then(function() { - // If not resolved yet, reject with timeout error. - if (!self.redirectedUserPromise_) { - // Consider redirect result resolved. - self.redirectResultResolved_ = true; - self.setRedirectResult_(true, null, error); - } - }); - -}; - - - -/** - * Popup Auth event manager. - * @param {!fireauth.AuthEventManager} manager The parent Auth event manager. - * @constructor - * @implements {fireauth.AuthEventProcessor} - */ -fireauth.PopupAuthEventProcessor = function(manager) { - this.manager_ = manager; -}; - - -/** - * Completes the processing of an external Auth event detected by the embedded - * iframe. - * @param {?fireauth.AuthEvent} authEvent External Auth event detected by - * iframe. - * @param {!fireauth.AuthEventHandler} owner The owner of the event. - * @return {!goog.Promise} - * @override - */ -fireauth.PopupAuthEventProcessor.prototype.processAuthEvent = - function(authEvent, owner) { - // This should not happen as fireauth.iframe.AuthRelay will not send null - // events. - if (!authEvent) { - return goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT)); - } - var mode = authEvent.getType(); - var eventId = authEvent.getEventId(); - if (authEvent.hasError()) { - return this.processErrorEvent_(authEvent, owner); - } else if (owner.getAuthEventHandlerFinisher(mode, eventId)) { - return this.processSuccessEvent_(authEvent, owner); - } else { - return goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT)); - } -}; - - -/** - * Processes an error event. - * @param {?fireauth.AuthEvent} authEvent External Auth event detected by - * iframe. - * @param {!fireauth.AuthEventHandler} owner The owner of the event. - * @return {!goog.Promise} - * @private - */ -fireauth.PopupAuthEventProcessor.prototype.processErrorEvent_ = - function(authEvent, owner) { - var eventId = authEvent.getEventId(); - var mode = authEvent.getType(); - // For pending popup promises trigger rejects with the error. - owner.resolvePendingPopupEvent(mode, null, authEvent.getError(), eventId); - return goog.Promise.resolve(); -}; - - -/** - * Processes a successful event. - * @param {?fireauth.AuthEvent} authEvent External Auth event detected by - * iframe. - * @param {!fireauth.AuthEventHandler} owner The owner of the event. - * @return {!goog.Promise} - * @private - */ -fireauth.PopupAuthEventProcessor.prototype.processSuccessEvent_ = - function(authEvent, owner) { - var eventId = authEvent.getEventId(); - var mode = authEvent.getType(); - var handler = owner.getAuthEventHandlerFinisher(mode, eventId); - // Successful operation, complete the exchange for an ID token. - var requestUri = /** @type {string} */ (authEvent.getUrlResponse()); - var sessionId = /** @type {string} */ (authEvent.getSessionId()); - var postBody = /** @type {?string} */ (authEvent.getPostBody()); - var tenantId = /** @type {?string} */ (authEvent.getTenantId()); - // Complete sign in or link account operation and then pass result to - // relevant pending popup promise. - return handler(requestUri, sessionId, tenantId, postBody) - .then(function(popupRedirectResponse) { - // Flow completed. - // Resolve pending popup promise if it exists. - owner.resolvePendingPopupEvent(mode, popupRedirectResponse, null, eventId); - }).thenCatch(function(error) { - // Flow not completed due to error. - // Resolve pending promise if it exists. - owner.resolvePendingPopupEvent( - mode, null, /** @type {!fireauth.AuthError} */ (error), eventId); - // Always resolve. - return; - }); -}; - - - -/** - * The interface that represents an Auth event handler. It provides the - * ability for the Auth event manager to determine the owner of an Auth event, - * the ability to resolve a pending popup event and the appropriate handler for - * an event. - * @interface - */ -fireauth.AuthEventHandler = function() {}; - - -/** - * @param {!fireauth.AuthEvent.Type} mode The Auth type mode. - * @param {?string=} opt_eventId The event ID. - * @return {boolean} Whether the Auth event handler can handler the provided - * event. - */ -fireauth.AuthEventHandler.prototype.canHandleAuthEvent = - function(mode, opt_eventId) {}; - - -/** - * Completes the pending popup operation. If error is not null, rejects with the - * error. Otherwise, it resolves with the popup redirect result. - * @param {!fireauth.AuthEvent.Type} mode The Auth type mode. - * @param {?fireauth.AuthEventManager.Result} popupRedirectResult The result - * to resolve with when no error supplied. - * @param {?fireauth.AuthError} error When supplied, the promise will reject. - * @param {?string=} opt_eventId The event ID. - */ -fireauth.AuthEventHandler.prototype.resolvePendingPopupEvent = - function(mode, popupRedirectResult, error, opt_eventId) {}; - - -/** - * Returns the handler's appropriate popup and redirect sign in operation - * finisher. - * @param {!fireauth.AuthEvent.Type} mode The Auth type mode. - * @param {?string=} opt_eventId The optional event ID. - * @return {?function(string, string, ?string, - * ?string=):!goog.Promise} - */ -fireauth.AuthEventHandler.prototype.getAuthEventHandlerFinisher = - function(mode, opt_eventId) {}; diff --git a/packages/auth/src/authsettings.js b/packages/auth/src/authsettings.js deleted file mode 100644 index 8fe41fcb3d4..00000000000 --- a/packages/auth/src/authsettings.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Defines the firebase.auth.AuthSettings structure. - */ - -goog.provide('fireauth.AuthSettings'); - - -/** - * The class used to initialize an Auth settings object currently used to - * enable or disable app verification for testing. - * @constructor - */ -fireauth.AuthSettings = function() { - this.appVerificationDisabledForTesting_ = false; - Object.defineProperty( - /** @type {!Object} */ (this), - 'appVerificationDisabled', - { - /** - * @this {!Object} - * @return {boolean} The current status. - */ - get: function() { - return this.getAppVerificationDisabledForTesting(); - }, - /** - * @this {!Object} - * @param {boolean} value The new status. - */ - set: function(value) { - this.setAppVerificationDisabledForTesting(value); - }, - enumerable: false - }); -}; - - -/** - * Sets whether app verification is disable for testing. - * @param {boolean} status App verification status for testing. - */ -fireauth.AuthSettings.prototype.setAppVerificationDisabledForTesting = - function(status) { - this.appVerificationDisabledForTesting_ = status; -}; - - -/** - * @return {boolean} Whether app verification is enabled or disabled for - * testing. - */ -fireauth.AuthSettings.prototype.getAppVerificationDisabledForTesting = - function() { - return this.appVerificationDisabledForTesting_; -}; diff --git a/packages/auth/src/authstorage.js b/packages/auth/src/authstorage.js deleted file mode 100644 index f36f6245285..00000000000 --- a/packages/auth/src/authstorage.js +++ /dev/null @@ -1,658 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines utilities for session management. - */ - -goog.provide('fireauth.authStorage'); -goog.provide('fireauth.authStorage.Key'); -goog.provide('fireauth.authStorage.Manager'); -goog.provide('fireauth.authStorage.Persistence'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.storage.Factory'); -goog.require('fireauth.storage.IndexedDB'); -goog.require('fireauth.storage.Storage'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.array'); -goog.require('goog.events'); -goog.require('goog.object'); - - - -/** - * The namespace for Firebase Auth storage. - * @private @const {string} - */ -fireauth.authStorage.NAMESPACE_ = 'firebase'; - - -/** - * The separator for Firebase Auth storage with App ID key. - * @private @const {string} - */ -fireauth.authStorage.SEPARATOR_ = ':'; - - -/** - * @const {number} The IE 10 localStorage cross tab synchronization delay in - * milliseconds. - */ -fireauth.authStorage.IE10_LOCAL_STORAGE_SYNC_DELAY = 10; - - -/** - * Enums for Auth state persistence. - * @enum {string} - */ -fireauth.authStorage.Persistence = { - // State will persist even when the browser window is closed or the activity - // is destroyed in react-native. - LOCAL: 'local', - // State is only stored in memory and will be cleared when the window or - // activity is refreshed. - NONE: 'none', - // State will only persist in current session/tab, relevant to web only, and - // will be cleared when the tab is closed. - SESSION: 'session' -}; - - -/** - * Validates that an argument is a valid persistence value. If an invalid type - * is specified, an error is thrown synchronously. - * @param {*} arg The argument to validate. - */ -fireauth.authStorage.validatePersistenceArgument = - function(arg) { - // Invalid type error. - var invalidTypeError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_PERSISTENCE); - // Unsupported type error. - var unsupportedTypeError = new fireauth.AuthError( - fireauth.authenum.Error.UNSUPPORTED_PERSISTENCE); - // Check if the persistence type is a valid one. - // Throw invalid type error if not valid. - if (!goog.object.containsValue(fireauth.authStorage.Persistence, arg) || - // goog.object.containsValue(fireauth.authStorage.Persistence, ['none']) - // returns true. - typeof arg !== 'string') { - throw invalidTypeError; - } - // Validate if the specified type is supported in the current environment. - switch (fireauth.util.getEnvironment()) { - case fireauth.util.Env.REACT_NATIVE: - // This is only supported in a browser. - if (arg === fireauth.authStorage.Persistence.SESSION) { - throw unsupportedTypeError; - } - break; - case fireauth.util.Env.NODE: - // Only none is supported in Node.js. - if (arg !== fireauth.authStorage.Persistence.NONE) { - throw unsupportedTypeError; - } - break; - case fireauth.util.Env.WORKER: - // In a worker environment, either LOCAL or NONE are supported. - // If indexedDB not supported and LOCAL provided, throw an error. - if (arg === fireauth.authStorage.Persistence.SESSION || - (!fireauth.storage.IndexedDB.isAvailable() && - arg !== fireauth.authStorage.Persistence.NONE)) { - throw unsupportedTypeError; - } - break; - case fireauth.util.Env.BROWSER: - default: - // This is restricted by what the browser supports. - if (!fireauth.util.isWebStorageSupported() && - arg !== fireauth.authStorage.Persistence.NONE) { - throw unsupportedTypeError; - } - break; - } -}; - - -/** - * Storage key metadata. - * @typedef {{name: string, persistent: !fireauth.authStorage.Persistence}} - */ -fireauth.authStorage.Key; - - -/** - * Storage manager. - * @param {string} namespace The optional namespace. - * @param {string} separator The optional separator. - * @param {boolean} safariLocalStorageNotSynced Whether browser has Safari - * iframe restriction with storage event triggering but storage not updated. - * @param {boolean} runsInBackground Whether browser can detect storage event - * when it had already been pushed to the background. This may happen in - * some mobile browsers. A localStorage change in the foreground window - * will not be detected in the background window via the storage event. - * This was detected in iOS 7.x mobile browsers. - * @param {boolean} webStorageSupported Whether browser web storage is - * supported. - * @constructor @struct @final - */ -fireauth.authStorage.Manager = function( - namespace, - separator, - safariLocalStorageNotSynced, - runsInBackground, - webStorageSupported) { - /** @const @private {string} Storage namespace. */ - this.namespace_ = namespace; - /** @const @private {string} Storage namespace key separator. */ - this.separator_ = separator; - /** - * @const @private {boolean} Whether browser has Safari iframe restriction - * with storage event triggering but storage not updated. - */ - this.safariLocalStorageNotSynced_ = safariLocalStorageNotSynced; - /** - * @private {boolean} Whether browser can detect storage event when it - * had already been pushed to the background. This may happen in some - * mobile browsers. - */ - this.runsInBackground_ = runsInBackground; - /** @const @private {boolean} Whether browser web storage is supported. */ - this.webStorageSupported_ = webStorageSupported; - - /** - * @const @private {!Object.>} The storage event - * key to listeners map. - */ - this.listeners_ = {}; - - var storageFactory = fireauth.storage.Factory.getInstance(); - try { - /** - * @private {!fireauth.storage.Storage} Persistence storage. - */ - this.persistentStorage_ = storageFactory.makePersistentStorage(); - } catch (e) { - // Default to in memory storage if the preferred persistent storage is not - // supported. - this.persistentStorage_ = storageFactory.makeInMemoryStorage(); - // Do not use indexedDB fallback. - this.localStorageNotSynchronized_ = false; - // Do not set polling functions on window.localStorage. - this.runsInBackground_ = true; - } - try { - /** - * @private {!fireauth.storage.Storage} Temporary session storage. - */ - this.temporaryStorage_ = storageFactory.makeTemporaryStorage(); - } catch (e) { - // Default to in memory storage if the preferred temporary storage is not - // supported. This should be a different in memory instance as the - // persistent storage, since the same key could be available for both types - // of storage. - this.temporaryStorage_ = storageFactory.makeInMemoryStorage(); - } - /** - * @private {!fireauth.storage.Storage} In memory storage. - */ - this.inMemoryStorage_ = storageFactory.makeInMemoryStorage(); - - /** - * @const @private {function(!goog.events.BrowserEvent)| - * function(!Array)} Storage change handler. - */ - this.storageChangeEventHandler_ = goog.bind(this.storageChangeEvent_, this); - /** @private {!Object.} Local map for localStorage. */ - this.localMap_ = {}; -}; - - -/** - * @return {!fireauth.authStorage.Manager} The default Auth storage manager - * instance. - */ -fireauth.authStorage.Manager.getInstance = function() { - // Creates the default instance for Auth storage maanger. - if (!fireauth.authStorage.Manager.instance_) { - /** - * @private {?fireauth.authStorage.Manager} The default storage manager - * instance. - */ - fireauth.authStorage.Manager.instance_ = new fireauth.authStorage.Manager( - fireauth.authStorage.NAMESPACE_, - fireauth.authStorage.SEPARATOR_, - fireauth.util.isSafariLocalStorageNotSynced(), - fireauth.util.runsInBackground(), - fireauth.util.isWebStorageSupported()); - } - return fireauth.authStorage.Manager.instance_; -}; - - -/** Clears storage manager instances. This is used for testing. */ -fireauth.authStorage.Manager.clear = function() { - fireauth.authStorage.Manager.instance_ = null; -}; - - -/** - * Returns the storage corresponding to the specified persistence. - * @param {!fireauth.authStorage.Persistence} persistent The type of storage - * persistence. - * @return {!fireauth.storage.Storage} The corresponding storage instance. - * @private - */ -fireauth.authStorage.Manager.prototype.getStorage_ = function(persistent) { - switch (persistent) { - case fireauth.authStorage.Persistence.SESSION: - return this.temporaryStorage_; - case fireauth.authStorage.Persistence.NONE: - return this.inMemoryStorage_; - case fireauth.authStorage.Persistence.LOCAL: - default: - return this.persistentStorage_; - } -}; - - -/** - * Constructs the corresponding storage key name. - * @param {fireauth.authStorage.Key} dataKey The key under which the value is - * stored. - * @param {?string=} opt_id This ID associates storage values with specific - * apps. - * @return {string} The corresponding key name with namespace prefixed. - * @private - */ -fireauth.authStorage.Manager.prototype.getKeyName_ = function(dataKey, opt_id) { - return this.namespace_ + this.separator_ + dataKey.name + - (opt_id ? this.separator_ + opt_id : ''); -}; - - -/** - * Migrates window.localStorage to the provided persistent storage. - * @param {fireauth.authStorage.Key} dataKey The key under which the persistent - * value is supposed to be stored. - * @param {?string=} opt_id When operating in multiple app mode, this ID - * associates storage values with specific apps. - * @return {!goog.Promise} A promise that resolves when the data stored - * in window.localStorage is migrated to the provided persistent storage - * identified by the provided data key. - */ -fireauth.authStorage.Manager.prototype.migrateFromLocalStorage = - function(dataKey, opt_id) { - var self = this; - var key = this.getKeyName_(dataKey, opt_id); - var storage = this.getStorage_(dataKey.persistent); - // Get data stored in the default persistent storage identified by dataKey. - return this.get(dataKey, opt_id).then(function(response) { - // Get the stored value in window.localStorage if available. - var oldStorageValue = null; - try { - oldStorageValue = fireauth.util.parseJSON( - goog.global['localStorage']['getItem'](key)); - } catch (e) { - // Set value as null. This will resolve the promise immediately. - } - // If data is stored in window.localStorage but no data is available in - // default persistent storage, migrate data from window.localStorage to - // default persistent storage. - if (oldStorageValue && !response) { - // This condition may fail in situations where a user opens a tab with - // an old version while using a tab with a new version, or when a - // developer switches back and forth between and old and new version of - // the library. - goog.global['localStorage']['removeItem'](key); - // Migrate the value to new default persistent storage. - return self.set(dataKey, oldStorageValue, opt_id); - } else if (oldStorageValue && - response && - storage.type != fireauth.storage.Storage.Type.LOCAL_STORAGE) { - // Data stored in both localStorage and new persistent storage (eg. - // indexedDB) for some reason. - // This could happen if the developer is migrating back and forth. - // The new default persistent storage (eg. indexedDB) takes precedence. - goog.global['localStorage']['removeItem'](key); - } - }); -}; - - -/** - * Gets the stored value from the corresponding storage. - * @param {fireauth.authStorage.Key} dataKey The key under which the value is - * stored. - * @param {?string=} opt_id When operating in multiple app mode, this ID - * associates storage values with specific apps. - * @return {!goog.Promise} A Promise that resolves with the stored value. - */ -fireauth.authStorage.Manager.prototype.get = function(dataKey, opt_id) { - var keyName = this.getKeyName_(dataKey, opt_id); - return this.getStorage_(dataKey.persistent).get(keyName); -}; - - -/** - * Removes the stored value from the corresponding storage. - * @param {fireauth.authStorage.Key} dataKey The key under which the value is - * stored. - * @param {?string=} opt_id When operating in multiple app mode, this ID - * associates storage values with specific apps. - * @return {!goog.Promise} A Promise that resolves when the operation is - * completed. - */ -fireauth.authStorage.Manager.prototype.remove = function(dataKey, opt_id) { - var keyName = this.getKeyName_(dataKey, opt_id); - // Keep local map up to date for requested key if persistent storage is used. - if (dataKey.persistent == fireauth.authStorage.Persistence.LOCAL) { - this.localMap_[keyName] = null; - } - return this.getStorage_(dataKey.persistent).remove(keyName); -}; - - -/** - * Stores the value in the corresponding storage. - * @param {fireauth.authStorage.Key} dataKey The key under which the value is - * stored. - * @param {*} value The value to be stored. - * @param {?string=} opt_id When operating in multiple app mode, this ID - * associates storage values with specific apps. - * @return {!goog.Promise} A Promise that resolves when the operation is - * completed. - */ -fireauth.authStorage.Manager.prototype.set = function(dataKey, value, opt_id) { - var keyName = this.getKeyName_(dataKey, opt_id); - var self = this; - var storage = this.getStorage_(dataKey.persistent); - return storage.set(keyName, value) - .then(function() { - return storage.get(keyName); - }) - .then(function(serializedValue) { - // Keep local map up to date for requested key if persistent storage is - // used. - if (dataKey.persistent == fireauth.authStorage.Persistence.LOCAL) { - self.localMap_[keyName] = serializedValue; - } - }); -}; - - -/** - * @param {fireauth.authStorage.Key} dataKey The key under which the value is - * stored. - * @param {?string} id When operating in multiple app mode, this ID associates - * storage values with specific apps. - * @param {function()} listener The callback listener to run on storage event - * related to key. - */ -fireauth.authStorage.Manager.prototype.addListener = - function(dataKey, id, listener) { - var key = this.getKeyName_(dataKey, id); - // Initialize local map for current key if web storage is supported. - if (this.webStorageSupported_) { - this.localMap_[key] = goog.global['localStorage']['getItem'](key); - } - if (goog.object.isEmpty(this.listeners_)) { - // Start listeners. - this.startListeners_(); - } - if (!this.listeners_[key]) { - this.listeners_[key] = []; - } - this.listeners_[key].push(listener); -}; - - -/** - * @param {fireauth.authStorage.Key} dataKey The key under which the value is - * stored. - * @param {?string} id When operating in multiple app mode, this ID associates - * storage values with specific apps. - * @param {function()} listener The listener to remove. - */ -fireauth.authStorage.Manager.prototype.removeListener = - function(dataKey, id, listener) { - var key = this.getKeyName_(dataKey, id); - if (this.listeners_[key]) { - goog.array.removeAllIf( - this.listeners_[key], - function(ele) { - return ele == listener; - }); - if (this.listeners_[key].length == 0) { - delete this.listeners_[key]; - } - } - if (goog.object.isEmpty(this.listeners_)) { - // Stop listeners. - this.stopListeners_(); - } -}; - - -/** - * The delay to wait between continuous checks of localStorage on browsers where - * tabs do not run in the background. After each interval wait, we check for - * external changes in localStorage that were not detected in the current tab. - * @const {number} - * @private - */ -fireauth.authStorage.Manager.LOCAL_STORAGE_POLLING_TIMER_ = 1000; - - -/** - * Starts all storage event listeners. - * @private - */ -fireauth.authStorage.Manager.prototype.startListeners_ = function() { - this.getStorage_(fireauth.authStorage.Persistence.LOCAL) - .addStorageListener(this.storageChangeEventHandler_); - // TODO: refactor this implementation to be handled by the underlying - // storage mechanism. - if (!this.runsInBackground_ && - // Add an exception for browsers that persist storage with indexedDB, we - // should stick with indexedDB listener implementation in that case. - !fireauth.util.persistsStorageWithIndexedDB() && - // Confirm browser web storage is supported as polling relies on it. - this.webStorageSupported_) { - this.startManualListeners_(); - } -}; - -/** - * Starts manual polling function to detect storage event changes. - * @private - */ -fireauth.authStorage.Manager.prototype.startManualListeners_ = function() { - var self = this; - this.stopManualListeners_(); - /** @private {?number} The interval timer for manual storage checking. */ - this.manualListenerTimer_ = setInterval(function() { - // Check all keys with listeners on them. - for (var key in self.listeners_) { - // Get value from localStorage. - var currentValue = goog.global['localStorage']['getItem'](key); - var oldValue = self.localMap_[key]; - // If local map value does not match, trigger listener with storage event. - if (currentValue != oldValue) { - self.localMap_[key] = currentValue; - var event = new goog.events.BrowserEvent(/** @type {!Event} */ ({ - type: 'storage', - key: key, - target: window, - oldValue: oldValue, - newValue: currentValue, - // Differentiate this simulated event from the real storage event. - poll: true - })); - self.storageChangeEvent_(event); - } - } - }, fireauth.authStorage.Manager.LOCAL_STORAGE_POLLING_TIMER_); -}; - - -/** - * Stops manual polling function to detect storage event changes. - * @private - */ -fireauth.authStorage.Manager.prototype.stopManualListeners_ = function() { - if (this.manualListenerTimer_) { - clearInterval(this.manualListenerTimer_); - this.manualListenerTimer_ = null; - } -}; - - -/** - * Stops all storage event listeners. - * @private - */ -fireauth.authStorage.Manager.prototype.stopListeners_ = function() { - this.getStorage_(fireauth.authStorage.Persistence.LOCAL) - .removeStorageListener(this.storageChangeEventHandler_); - this.stopManualListeners_(); -}; - - -/** - * @param {!goog.events.BrowserEvent|!Array} data The storage event - * triggered or the array of keys modified. - * @private - */ -fireauth.authStorage.Manager.prototype.storageChangeEvent_ = function(data) { - if (data && data.getBrowserEvent) { - var event = /** @type {!goog.events.BrowserEvent} */ (data); - var key = event.getBrowserEvent().key; - // Key would be null in some situations, like when localStorage is cleared - // from the browser developer tools. - if (key == null) { - // For all keys of interest. - for (var keyName in this.listeners_) { - // Check if something changed in this key's real value. - var storedValue = this.localMap_[keyName]; - // localStorage returns null when a field is not found. - if (typeof storedValue === 'undefined') { - storedValue = null; - } - var realValue = goog.global['localStorage']['getItem'](keyName); - if (realValue !== storedValue) { - // Update local map with real value. - this.localMap_[keyName] = realValue; - // Trigger that key's listener. - this.callListeners_(keyName); - } - } - return; - } - // Check if the key is Firebase Auth related, otherwise ignore. - if (key.indexOf(this.namespace_ + this.separator_) != 0 || - // Ignore keys that have no listeners. - !this.listeners_[key]) { - return; - } - // Check the mechanism how this event was detected. - // The first event will dictate the mechanism to be used. - // Do not use hasOwnProperty('poll') as poll gets obfuscated. - if (typeof event.getBrowserEvent().poll !== 'undefined') { - // Environment detects storage changes via polling. - // Remove storage event listener to prevent possible event duplication. - this.getStorage_(fireauth.authStorage.Persistence.LOCAL) - .removeStorageListener(this.storageChangeEventHandler_); - } else { - // Environment detects storage changes via storage event listener. - // Remove polling listener to prevent possible event duplication. - this.stopManualListeners_(); - } - // Safari embedded iframe. Storage event will trigger with the delta changes - // but no changes will be applied to the iframe localStorage. - if (this.safariLocalStorageNotSynced_) { - // Get current iframe page value, old value and new value. - var currentValue = goog.global['localStorage']['getItem'](key); - var newValue = event.getBrowserEvent().newValue; - // Value not synchronized, synchronize manually. - if (newValue !== currentValue) { - if (newValue !== null) { - // Value changed from current value. - goog.global['localStorage']['setItem'](key, newValue); - } else { - // Current value deleted. - goog.global['localStorage']['removeItem'](key); - } - } else { - // Already detected and processed, do not trigger listeners again. - if (this.localMap_[key] === newValue && - // Real storage event. - typeof event.getBrowserEvent().poll === 'undefined') { - return; - } - } - } - var self = this; - var triggerListeners = function() { - // Keep local map up to date in case storage event is triggered before - // poll. - if (typeof event.getBrowserEvent().poll === 'undefined' && - self.localMap_[key] === goog.global['localStorage']['getItem'](key)) { - // Real storage event which has already been detected, do nothing. - // This seems to trigger in some IE browsers for some reason. - return; - } - self.localMap_[key] = goog.global['localStorage']['getItem'](key); - self.callListeners_(key); - }; - if (fireauth.util.isIe10() && - goog.global['localStorage']['getItem'](key) !== - event.getBrowserEvent().newValue && - event.getBrowserEvent().newValue !== event.getBrowserEvent().oldValue) { - // IE 10 has this weird bug where a storage event would trigger with the - // correct key, oldValue and newValue but localStorage.getItem(key) does - // not yield the updated value until a few milliseconds. This ensures this - // recovers from that situation. - setTimeout( - triggerListeners, fireauth.authStorage.IE10_LOCAL_STORAGE_SYNC_DELAY); - } else { - triggerListeners(); - } - } else { - var keys = /** @type {!Array} */ (data); - goog.array.forEach(keys, goog.bind(this.callListeners_, this)); - } -}; - - -/** - * Calls all listeners for specified storage event key. - * @param {string} key The storage event key whose listeners are to be run. - * @private - */ -fireauth.authStorage.Manager.prototype.callListeners_ = function(key) { - if (this.listeners_[key]) { - goog.array.forEach( - this.listeners_[key], - function(listener) { - listener(); - }); - } -}; diff --git a/packages/auth/src/authuser.js b/packages/auth/src/authuser.js deleted file mode 100644 index 49314f36b49..00000000000 --- a/packages/auth/src/authuser.js +++ /dev/null @@ -1,2556 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the user info pertaining to an identity provider and - * the Firebase user object. - */ - -goog.provide('fireauth.AuthUser'); -goog.provide('fireauth.AuthUser.AccountInfo'); -goog.provide('fireauth.AuthUserInfo'); -goog.provide('fireauth.TokenRefreshTime'); -goog.provide('fireauth.UserMetadata'); - -goog.require('fireauth.ActionCodeSettings'); -goog.require('fireauth.AdditionalUserInfo'); -goog.require('fireauth.AuthCredential'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.AuthEventHandler'); -goog.require('fireauth.AuthEventManager'); -goog.require('fireauth.AuthProvider'); -goog.require('fireauth.ConfirmationResult'); -goog.require('fireauth.IdTokenResult'); -goog.require('fireauth.MultiFactorError'); -goog.require('fireauth.MultiFactorUser'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.ProactiveRefresh'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.StsTokenManager'); -goog.require('fireauth.UserEvent'); -goog.require('fireauth.UserEventType'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.constants.AuthEventType'); -goog.require('fireauth.deprecation'); -goog.require('fireauth.idp'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.object'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.array'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventTarget'); -goog.require('goog.object'); - - - -/** - * Initializes an instance of a user metadata object. - * @param {?string=} opt_createdAt The optional creation date UTC timestamp. - * @param {?string=} opt_lastLoginAt The optional last login date UTC timestamp. - * @constructor - */ -fireauth.UserMetadata = function(opt_createdAt, opt_lastLoginAt) { - /** @private {?string} The created at UTC timestamp. */ - this.createdAt_ = opt_createdAt || null; - /** @private {?string} The last login at UTC timestamp. */ - this.lastLoginAt_ = opt_lastLoginAt || null; - fireauth.object.setReadonlyProperties(this, { - 'lastSignInTime': fireauth.util.utcTimestampToDateString( - opt_lastLoginAt || null), - 'creationTime': fireauth.util.utcTimestampToDateString( - opt_createdAt || null), - }); -}; - - -/** - * @return {!fireauth.UserMetadata} A clone of the current user metadata object. - */ -fireauth.UserMetadata.prototype.clone = function() { - return new fireauth.UserMetadata(this.createdAt_, this.lastLoginAt_); -}; - - -/** - * @return {!Object} The object representation of the user metadata instance. - */ -fireauth.UserMetadata.prototype.toPlainObject = function() { - return { - 'lastLoginAt': this.lastLoginAt_, - 'createdAt': this.createdAt_ - }; -}; - - -/** - * Initializes an instance of the user info for an identity provider. - * @param {string} uid The user ID. - * @param {!fireauth.idp.ProviderId} providerId The provider ID. - * @param {?string=} opt_email The optional user email. - * @param {?string=} opt_displayName The optional display name. - * @param {?string=} opt_photoURL The optional photo URL. - * @param {?string=} opt_phoneNumber The optional phone number. - * @constructor - */ -fireauth.AuthUserInfo = function( - uid, - providerId, - opt_email, - opt_displayName, - opt_photoURL, - opt_phoneNumber) { - fireauth.object.setReadonlyProperties(this, { - 'uid': uid, - 'displayName': opt_displayName || null, - 'photoURL': opt_photoURL || null, - 'email': opt_email || null, - 'phoneNumber': opt_phoneNumber || null, - 'providerId': providerId - }); -}; - - -/** - * Defines the proactive token refresh time constraints in milliseconds. - * @enum {number} - */ -fireauth.TokenRefreshTime = { - /** - * The offset time before token natural expiration to run the refresh. - * This is currently 5 minutes. - */ - OFFSET_DURATION: 5 * 60 * 1000, - /** - * This is the first retrial wait after an error. This is currently - * 30 seconds. - */ - RETRIAL_MIN_WAIT: 30 * 1000, - /** - * This is the maximum retrial wait, currently 16 minutes. - */ - RETRIAL_MAX_WAIT: 16 * 60 * 1000 -}; - - - -/** - * The Firebase user. - * @param {!Object} appOptions The application options. - * @param {!Object} stsTokenResponse The server STS token response. - * @param {?Object=} opt_accountInfo The optional user account info. - * @constructor - * @extends {goog.events.EventTarget} - * @implements {fireauth.AuthEventHandler} - */ -fireauth.AuthUser = - function(appOptions, stsTokenResponse, opt_accountInfo) { - /** @private {!Array|!goog.Promise>} List of pending - * promises. */ - this.pendingPromises_ = []; - // User is only created via Auth so API key should always be available. - /** @private {string} The API key. */ - this.apiKey_ = /** @type {string} */ (appOptions['apiKey']); - // This is needed to associate a user to the corresponding Auth instance. - /** @private {string} The App name. */ - this.appName_ = /** @type {string} */ (appOptions['appName']); - /** @private {?string} The Auth domain. */ - this.authDomain_ = appOptions['authDomain'] || null; - var clientFullVersion = firebase.SDK_VERSION ? - fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION) : - null; - /** @private {!fireauth.RpcHandler} The RPC handler instance. */ - this.rpcHandler_ = new fireauth.RpcHandler( - this.apiKey_, - // Get the client Auth endpoint used. - fireauth.constants.getEndpointConfig(fireauth.constants.clientEndpoint), - clientFullVersion); - /** @private {?fireauth.constants.EmulatorSettings} The emulator config */ - this.emulatorConfig_ = appOptions['emulatorConfig'] || null; - if (this.emulatorConfig_) { - this.rpcHandler_.updateEmulatorConfig(this.emulatorConfig_); - } - // TODO: Consider having AuthUser take a fireauth.StsTokenManager - // instance instead of a token response but make sure lastAccessToken_ also - // initialized at the right time. In this case initializeFromIdTokenResponse - // will take in a token response object and convert it to an instance of - // fireauth.StsTokenManager to properly initialize user. - /** @private {!fireauth.StsTokenManager} The STS token manager instance. */ - this.stsTokenManager_ = new fireauth.StsTokenManager(this.rpcHandler_); - - this.setLastAccessToken_( - stsTokenResponse[fireauth.RpcHandler.AuthServerField.ID_TOKEN]); - // STS token manager will always be populated using server response. - this.stsTokenManager_.parseServerResponse(stsTokenResponse); - fireauth.object.setReadonlyProperty( - this, 'refreshToken', this.stsTokenManager_.getRefreshToken()); - this.setAccountInfo(/** @type {!fireauth.AuthUser.AccountInfo} */ ( - opt_accountInfo || {})); - // Add call to superclass constructor. - fireauth.AuthUser.base(this, 'constructor'); - /** @private {boolean} Whether popup and redirect is enabled on the user. */ - this.popupRedirectEnabled_ = false; - if (this.authDomain_ && - fireauth.AuthEventManager.ENABLED && - // Make sure popup and redirects are supported in the current environment. - fireauth.util.isPopupRedirectSupported()) { - // Get the Auth event manager associated with this user. - this.authEventManager_ = fireauth.AuthEventManager.getManager( - this.authDomain_, this.apiKey_, this.appName_, this.emulatorConfig_); - } - /** @private {!Array} The list of - * state change listeners. This is needed to make sure state changes are - * resolved before resolving user API promises. For example redirect - * operations should make sure the associated event ID is saved before - * redirecting. - */ - this.stateChangeListeners_ = []; - /** - * @private {?fireauth.AuthError} The user invalidation error if it exists. - */ - this.userInvalidatedError_ = null; - /** - * @private {!fireauth.ProactiveRefresh} The reference to the proactive token - * refresher utility for the current user. - */ - this.proactiveRefresh_ = this.initializeProactiveRefreshUtility_(); - /** - * @private {!function(!Object)} The handler for user token changes used to - * realign the proactive token refresh with external token refresh calls. - */ - this.userTokenChangeListener_ = goog.bind(this.handleUserTokenChange_, this); - var self = this; - /** @private {?string} The current user's language code. */ - this.languageCode_ = null; - /** - * @private {function(!goog.events.Event)} The on language code changed event - * handler. - */ - this.onLanguageCodeChanged_ = function(event) { - // Update the user language code. - self.setLanguageCode(event.languageCode); - }; - /** - * @private {?goog.events.EventTarget} The language code change event - * dispatcher. - */ - this.languageCodeChangeEventDispatcher_ = null; - - /** - * @private {function(!goog.events.Event)} The on emulator config changed - * event handler. - */ - this.onEmulatorConfigChanged_ = function (event) { - // Update the emulator config. - self.setEmulatorConfig(event.emulatorConfig); - }; - /** - * @private {?goog.events.EventTarget} The emulator code change event - * dispatcher. - */ - this.emulatorConfigChangeEventDispatcher_ = null; - - /** @private {!Array} The current Firebase frameworks. */ - this.frameworks_ = []; - /** - * @private {function(!goog.events.Event)} The on framework list changed event - * handler. - */ - this.onFrameworkChanged_ = function(event) { - // Update the Firebase frameworks. - self.setFramework(event.frameworks); - }; - /** - * @private {?goog.events.EventTarget} The framework change event dispatcher. - */ - this.frameworkChangeEventDispatcher_ = null; - /** - * @const @private {!fireauth.MultiFactorUser} The multifactor user instance. - */ - this.multiFactorUser_ = new fireauth.MultiFactorUser( - this, /** @type {?fireauth.AuthUser.AccountInfo|undefined} */ ( - opt_accountInfo)); - fireauth.object.setReadonlyProperty( - this, 'multiFactor', this.multiFactorUser_); -}; -goog.inherits(fireauth.AuthUser, goog.events.EventTarget); - - -/** - * Updates the user language code. - * @param {?string} languageCode The current language code to use in user - * requests. - */ -fireauth.AuthUser.prototype.setLanguageCode = function(languageCode) { - // Save current language. - this.languageCode_ = languageCode; - // Update the custom locale header. - this.rpcHandler_.updateCustomLocaleHeader(languageCode); -}; - - -/** - * Updates the emulator config. - * @param {?fireauth.constants.EmulatorSettings} emulatorConfig The current - * emulator config to use in user requests. - */ -fireauth.AuthUser.prototype.setEmulatorConfig = function(emulatorConfig) { - // Update the emulator config. - this.emulatorConfig_ = emulatorConfig; - this.rpcHandler_.updateEmulatorConfig(emulatorConfig); - - if (this.authEventManager_) { - // We need to get a new auth event manager keyed with the new emulator - // config. - const oldManager = this.authEventManager_; - - // If authEventManager_ was previously set, we know authDomain_ is set as - // well. - this.authEventManager_ = fireauth.AuthEventManager.getManager( - /** @type {string} */ (this.authDomain_), this.apiKey_, this.appName_, - this.emulatorConfig_); - if (this.popupRedirectEnabled_) { - oldManager.unsubscribe(this); - this.authEventManager_.subscribe(this); - } - } -}; - - -/** @return {?string} The current user's language code. */ -fireauth.AuthUser.prototype.getLanguageCode = function() { - return this.languageCode_; -}; - - -/** - * Listens to language code changes triggered by the provided dispatcher. - * @param {?goog.events.EventTarget} dispatcher The language code changed event - * dispatcher. - */ -fireauth.AuthUser.prototype.setLanguageCodeChangeDispatcher = - function(dispatcher) { - // Remove any previous listener. - if (this.languageCodeChangeEventDispatcher_) { - goog.events.unlisten( - this.languageCodeChangeEventDispatcher_, - fireauth.constants.AuthEventType.LANGUAGE_CODE_CHANGED, - this.onLanguageCodeChanged_); - } - // Update current dispatcher. - this.languageCodeChangeEventDispatcher_ = dispatcher; - // Using an event listener makes it easy for non-currentUsers to detect - // language changes on the parent Auth instance. A developer could still call - // APIs that require localization on signed out user references. - if (dispatcher) { - goog.events.listen( - dispatcher, - fireauth.constants.AuthEventType.LANGUAGE_CODE_CHANGED, - this.onLanguageCodeChanged_); - } -}; - - -/** - * Listens to emulator config changes triggered by the provided dispatcher. - * @param {?goog.events.EventTarget} dispatcher The emulator config changed - * event dispatcher. - */ -fireauth.AuthUser.prototype.setEmulatorConfigChangeDispatcher = function(dispatcher) { - // Remove any previous listener. - if (this.emulatorConfigChangeEventDispatcher_) { - goog.events.unlisten( - this.emulatorConfigChangeEventDispatcher_, - fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED, - this.onEmulatorConfigChanged_); - } - // Update current dispatcher. - this.emulatorConfigChangeEventDispatcher_ = dispatcher; - // Using an event listener makes it easy for non-currentUsers to detect - // emulator changes on the parent Auth instance. A developer could still - // call APIs that require emulation on signed out user references. - if (dispatcher) { - goog.events.listen( - dispatcher, fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED, - this.onEmulatorConfigChanged_); - } -} - - -/** - * Updates the Firebase frameworks on the current user. - * @param {!Array} framework The list of Firebase frameworks. - */ -fireauth.AuthUser.prototype.setFramework = function(framework) { - // Save current frameworks. - this.frameworks_ = framework; - // Update the client version in RPC handler with the new frameworks. - this.rpcHandler_.updateClientVersion(firebase.SDK_VERSION ? - fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION, - this.frameworks_) : - null); -}; - - -/** @return {!Array} The current Firebase frameworks. */ -fireauth.AuthUser.prototype.getFramework = function() { - return goog.array.clone(this.frameworks_); -}; - - -/** - * Listens to framework changes triggered by the provided dispatcher. - * @param {?goog.events.EventTarget} dispatcher The framework changed event - * dispatcher. - */ -fireauth.AuthUser.prototype.setFrameworkChangeDispatcher = - function(dispatcher) { - // Remove any previous listener. - if (this.frameworkChangeEventDispatcher_) { - goog.events.unlisten( - this.frameworkChangeEventDispatcher_, - fireauth.constants.AuthEventType.FRAMEWORK_CHANGED, - this.onFrameworkChanged_); - } - // Update current dispatcher. - this.frameworkChangeEventDispatcher_ = dispatcher; - // Using an event listener makes it easy for non-currentUsers to detect - // framework changes on the parent Auth instance. - if (dispatcher) { - goog.events.listen( - dispatcher, - fireauth.constants.AuthEventType.FRAMEWORK_CHANGED, - this.onFrameworkChanged_); - } -}; - - -/** - * Handles user token changes. Currently used to realign the proactive token - * refresh internal timing with successful external token refreshes. - * @param {!Object} event The token change event. - * @private - */ -fireauth.AuthUser.prototype.handleUserTokenChange_ = function(event) { - // If an external service refreshes the token, reset the proactive token - // refresh utility in case it is still running so the next run time is - // up to date. - // This will currently also trigger when the proactive refresh succeeds. - // This is not ideal but should not have any downsides. It just adds a - // redundant reset which can be optimized not to run in the future. - if (this.proactiveRefresh_.isRunning()) { - this.proactiveRefresh_.stop(); - this.proactiveRefresh_.start(); - } -}; - - -/** - * @return {!fireauth.Auth} The corresponding Auth instance that created the - * current user. - * @private - */ -fireauth.AuthUser.prototype.getAuth_ = function() { - try { - // Get the Auth instance for the current app identified by the App name. - // This could fail if, for example, the App instance was deleted. - return firebase['app'](this.appName_)['auth'](); - } catch (e) { - // Throw appropriate error. - throw new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'No firebase.auth.Auth instance is available for the Firebase App ' + - '\'' + this.appName_ + '\'!'); - } -}; - - -/** - * @return {string} The user's API key. - */ -fireauth.AuthUser.prototype.getApiKey = function() { - return this.apiKey_; -}; - - -/** - * Returns the RPC handler of the user. - * @return {!fireauth.RpcHandler} The RPC handler. - */ -fireauth.AuthUser.prototype.getRpcHandler = function() { - return this.rpcHandler_; -}; - - -/** - * Used to initialize the current user's proactive token refresher utility. - * @return {!fireauth.ProactiveRefresh} The user's proactive token refresh - * utility. - * @private - */ -fireauth.AuthUser.prototype.initializeProactiveRefreshUtility_ = function() { - var self = this; - return new fireauth.ProactiveRefresh( - // Force ID token refresh right before expiration. - function() { - // Keep in mind when this fails for any reason other than a network - // error, it will effectively stop the proactive refresh. - return self.getIdToken(true); - }, - // Retry only on network errors. - function(error) { - if (error && error.code == 'auth/network-request-failed') { - return true; - } - return false; - }, - // Return next time to run with offset applied. - function() { - // Get time until expiration minus the refresh offset. - var waitInterval = - self.stsTokenManager_.getExpirationTime() - Date.now() - - fireauth.TokenRefreshTime.OFFSET_DURATION; - // Set to zero if wait interval is negative. - return waitInterval > 0 ? waitInterval : 0; - }, - // Retrial minimum wait. - fireauth.TokenRefreshTime.RETRIAL_MIN_WAIT, - // Retrial maximum wait. - fireauth.TokenRefreshTime.RETRIAL_MAX_WAIT, - // Do not run in background as it is common to have multiple tabs open - // in a browser and this could increase QPS on server. - false); -}; - - -/** Starts token proactive refresh. */ -fireauth.AuthUser.prototype.startProactiveRefresh = function() { - // Only allow if not destroyed and not already started. - if (!this.destroyed_ && !this.proactiveRefresh_.isRunning()) { - this.proactiveRefresh_.start(); - // Unlisten any previous token change listener. - goog.events.unlisten( - this, - fireauth.UserEventType.TOKEN_CHANGED, - this.userTokenChangeListener_); - // Listen to token changes to reset the token refresher. - goog.events.listen( - this, - fireauth.UserEventType.TOKEN_CHANGED, - this.userTokenChangeListener_); - } -}; - - -/** Stops token proactive refresh. */ -fireauth.AuthUser.prototype.stopProactiveRefresh = function() { - // Remove internal token change listener. - goog.events.unlisten( - this, - fireauth.UserEventType.TOKEN_CHANGED, - this.userTokenChangeListener_); - // Stop proactive token refresh. - this.proactiveRefresh_.stop(); -}; - - -/** - * Sets latest access token for the AuthUser object. - * @param {string} lastAccessToken - * @private - */ -fireauth.AuthUser.prototype.setLastAccessToken_ = function(lastAccessToken) { - /** @private {?string} Latest access token. */ - this.lastAccessToken_ = lastAccessToken; - fireauth.object.setReadonlyProperty(this, '_lat', lastAccessToken); -}; - - -/** - * @param {function(!fireauth.AuthUser):!goog.Promise} listener The listener - * to state changes to add. - */ -fireauth.AuthUser.prototype.addStateChangeListener = function(listener) { - this.stateChangeListeners_.push(listener); -}; - - -/** - * @param {function(!fireauth.AuthUser):!goog.Promise} listener The listener - * to state changes to remove. - */ -fireauth.AuthUser.prototype.removeStateChangeListener = function(listener) { - goog.array.removeAllIf(this.stateChangeListeners_, function(ele) { - return ele == listener; - }); -}; - - -/** - * Executes all state change listener promises and when all fulfilled, resolves - * with the current user. - * @return {!goog.Promise} A promise that resolves when all state listeners - * fulfilled. - * @private - */ -fireauth.AuthUser.prototype.notifyStateChangeListeners_ = function() { - var promises = []; - var self = this; - for (var i = 0; i < this.stateChangeListeners_.length; i++) { - // Run listener with Auth user instance and add to list of promises. - promises.push(this.stateChangeListeners_[i](this)); - } - return goog.Promise.allSettled(promises).then(function(results) { - // State change errors should be recoverable even if errors occur. - return self; - }); -}; - - -/** - * Sets the user current pending popup event ID. - * @param {string} eventId The pending popup event ID. - */ -fireauth.AuthUser.prototype.setPopupEventId = function(eventId) { - // Saving a popup event in a separate property other than redirectEventId - // would prevent a pending redirect event from being overwritten by a newly - // called popup operation. - this.popupEventId_ = eventId; -}; - - -/** - * @return {?string} The pending popup event ID. - */ -fireauth.AuthUser.prototype.getPopupEventId = function() { - return this.popupEventId_ || null; -}; - - -/** - * Sets the user current pending redirect event ID. - * @param {string} eventId The pending redirect event ID. - */ -fireauth.AuthUser.prototype.setRedirectEventId = function(eventId) { - this.redirectEventId_ = eventId; -}; - - -/** - * @return {?string} The pending redirect event ID. - */ -fireauth.AuthUser.prototype.getRedirectEventId = function() { - return this.redirectEventId_ || null; -}; - - -/** - * Subscribes to Auth event manager to handle popup and redirect events. - * This is an explicit operation as users could exist in temporary states. For - * example a user change could be detected in another tab. When syncing to those - * changes, a temporary user is retrieved from storage and then copied to - * existing user. The temporary user should not subscribe to Auth event changes. - */ -fireauth.AuthUser.prototype.enablePopupRedirect = function() { - // Subscribe to Auth event manager if available. - if (this.authEventManager_ && !this.popupRedirectEnabled_) { - this.popupRedirectEnabled_ = true; - this.authEventManager_.subscribe(this); - } -}; - - -/** - * getAccountInfo users field. - * @const {string} - */ -fireauth.AuthUser.GET_ACCOUNT_INFO_USERS = 'users'; - - -/** - * getAccountInfo response user fields. - * @enum {string} - */ -fireauth.AuthUser.GetAccountInfoField = { - CREATED_AT: 'createdAt', - DISPLAY_NAME: 'displayName', - EMAIL: 'email', - EMAIL_VERIFIED: 'emailVerified', - LAST_LOGIN_AT: 'lastLoginAt', - LOCAL_ID: 'localId', - PASSWORD_HASH: 'passwordHash', - PASSWORD_UPDATED_AT: 'passwordUpdatedAt', - PHONE_NUMBER: 'phoneNumber', - PHOTO_URL: 'photoUrl', - PROVIDER_USER_INFO: 'providerUserInfo', - TENANT_ID: 'tenantId' -}; - - -/** - * setAccountInfo response user fields. - * @enum {string} - */ -fireauth.AuthUser.SetAccountInfoField = { - DISPLAY_NAME: 'displayName', - EMAIL: 'email', - PHOTO_URL: 'photoUrl', - PROVIDER_ID: 'providerId', - PROVIDER_USER_INFO: 'providerUserInfo' -}; - - -/** - * getAccountInfo response provider user info fields. - * @enum {string} - */ -fireauth.AuthUser.GetAccountInfoProviderField = { - DISPLAY_NAME: 'displayName', - EMAIL: 'email', - PHOTO_URL: 'photoUrl', - PHONE_NUMBER: 'phoneNumber', - PROVIDER_ID: 'providerId', - RAW_ID: 'rawId' -}; - - -/** - * verifyAssertion response fields. - * @enum {string} - */ -fireauth.AuthUser.VerifyAssertionField = { - ID_TOKEN: 'idToken', - PROVIDER_ID: 'providerId' -}; - - -/** @return {!fireauth.StsTokenManager} The STS token manager instance */ -fireauth.AuthUser.prototype.getStsTokenManager = function() { - return this.stsTokenManager_; -}; - - -/** - * Sets the user account info. - * @param {!fireauth.AuthUser.AccountInfo} accountInfo The account information - * from the default provider. - */ -fireauth.AuthUser.prototype.setAccountInfo = function(accountInfo) { - fireauth.object.setReadonlyProperties(this, { - 'uid': accountInfo['uid'], - 'displayName': accountInfo['displayName'] || null, - 'photoURL': accountInfo['photoURL'] || null, - 'email': accountInfo['email'] || null, - 'emailVerified': accountInfo['emailVerified'] || false, - 'phoneNumber': accountInfo['phoneNumber'] || null, - 'isAnonymous': accountInfo['isAnonymous'] || false, - 'tenantId': accountInfo['tenantId'] || null, - 'metadata': new fireauth.UserMetadata( - accountInfo['createdAt'], accountInfo['lastLoginAt']), - 'providerData': [] - }); - // Sets the tenant ID on RPC handler. For requests with ID tokens, the source - // of truth is the tenant ID in the ID token. If the request body has a - // tenant ID (optional here), the backend will confirm it matches the - // tenant ID in the ID token, otherwise throw an error. If no tenant ID is - // passed in the request, it will be determined from the ID token. - this.rpcHandler_.updateTenantId(this['tenantId']); -}; - - -/** - * Type specifying the parameters that can be passed to the - * {@code fireauth.AuthUser} constructor. - * @typedef {{ - * uid: (?string|undefined), - * displayName: (?string|undefined), - * photoURL: (?string|undefined), - * email: (?string|undefined), - * emailVerified: ?boolean, - * phoneNumber: (?string|undefined), - * isAnonymous: ?boolean, - * createdAt: (?string|undefined), - * lastLoginAt: (?string|undefined), - * tenantId: (?string|undefined), - * multiFactor: ({ - * enrolledFactors: (?Array|undefined) - * }|undefined) - * }} - */ -fireauth.AuthUser.AccountInfo; - - -/** - * The provider for all fireauth.AuthUser objects is 'firebase'. - */ -fireauth.object.setReadonlyProperty(fireauth.AuthUser.prototype, 'providerId', - fireauth.idp.ProviderId.FIREBASE); - - -/** - * Returns nothing. This can be used to consume the output of a Promise. - * @private - */ -fireauth.AuthUser.returnNothing_ = function() { - // Return nothing. Intentionally left empty. -}; - - -/** - * Ensures the user is still logged in before moving to the next promise - * resolution. - * @return {!goog.Promise} - * @private - */ -fireauth.AuthUser.prototype.checkDestroyed_ = function() { - var self = this; - return goog.Promise.resolve().then(function() { - if (self.destroyed_) { - throw new fireauth.AuthError(fireauth.authenum.Error.MODULE_DESTROYED); - } - }); -}; - - -/** - * @return {!Array} The list of provider IDs. - */ -fireauth.AuthUser.prototype.getProviderIds = function() { - return goog.array.map(this['providerData'], function(userInfo) { - return userInfo['providerId']; - }); -}; - - -/** - * Adds the provided user info to list of providers' data. - * @param {?fireauth.AuthUserInfo} providerData Provider data to store for user. - */ -fireauth.AuthUser.prototype.addProviderData = function(providerData) { - if (!providerData) { - return; - } - this.removeProviderData(providerData['providerId']); - this['providerData'].push(providerData); -}; - - -/** - * @param {!fireauth.idp.ProviderId} providerId The provider ID whose - * data should be removed. - */ -fireauth.AuthUser.prototype.removeProviderData = function(providerId) { - goog.array.removeAllIf(this['providerData'], function(userInfo) { - return userInfo['providerId'] == providerId; - }); -}; - - -/** - * @param {string} propName The property name to modify. - * @param {?string|boolean} value The new value to set. - */ -fireauth.AuthUser.prototype.updateProperty = function(propName, value) { - // User ID is required. - if (propName == 'uid' && !value) { - return; - } - if (this.hasOwnProperty(propName)) { - fireauth.object.setReadonlyProperty(this, propName, value); - } -}; - - -/** - * @param {!fireauth.AuthUser} otherUser The other user to compare to. - * @return {boolean} True if both User objects have the same user ID. - */ -fireauth.AuthUser.prototype.hasSameUserIdAs = function(otherUser) { - var thisId = this['uid']; - var thatId = otherUser['uid']; - if (thisId === undefined || thisId === null || thisId === '' || - thatId === undefined || thatId === null || thatId === '') { - return false; - } - return thisId == thatId; -}; - - -/** - * Copies all properties and STS token manager instance from userToCopy to - * current user without triggering any Auth state change or token change - * listener. - * @param {!fireauth.AuthUser} userToCopy The updated user to overwrite current - * user. - */ -fireauth.AuthUser.prototype.copy = function(userToCopy) { - var self = this; - // Copy to self. - if (self == userToCopy) { - return; - } - fireauth.object.setReadonlyProperties(this, { - 'uid': userToCopy['uid'], - 'displayName': userToCopy['displayName'], - 'photoURL': userToCopy['photoURL'], - 'email': userToCopy['email'], - 'emailVerified': userToCopy['emailVerified'], - 'phoneNumber': userToCopy['phoneNumber'], - 'isAnonymous': userToCopy['isAnonymous'], - 'tenantId': userToCopy['tenantId'], - 'providerData': [] - }); - // This should always be available but just in case there is a conflict with - // a user from an older version. - if (userToCopy['metadata']) { - fireauth.object.setReadonlyProperty( - this, - 'metadata', - /** @type{!fireauth.UserMetadata} */ (userToCopy['metadata']).clone()); - } else { - // User to copy has no metadata. Align with that. - fireauth.object.setReadonlyProperty( - this, 'metadata', new fireauth.UserMetadata()); - } - goog.array.forEach(userToCopy['providerData'], function(userInfo) { - self.addProviderData(userInfo); - }); - this.stsTokenManager_.copy(userToCopy.getStsTokenManager()); - fireauth.object.setReadonlyProperty( - this, 'refreshToken', this.stsTokenManager_.getRefreshToken()); - // Copy multi-factor info to current user. - // This should be backward compatible. - // If the userToCopy is loaded from an older version, multiFactorUser - // enrolled factors will be initialized empty and copied empty to current - // multiFactorUser. - this.multiFactorUser_.copy(userToCopy.multiFactorUser_); -}; - - -/** - * Set the Auth user redirect storage manager. - * @param {?fireauth.storage.RedirectUserManager} redirectStorageManager The - * utility used to store or delete the user on redirect. - */ -fireauth.AuthUser.prototype.setRedirectStorageManager = - function(redirectStorageManager) { - /** - * @private {?fireauth.storage.RedirectUserManager} The redirect user storage - * manager. - */ - this.redirectStorageManager_ = redirectStorageManager; -}; - - -/** - * Refreshes the current user, if signed in. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.reload = function() { - var self = this; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_(this.checkDestroyed_().then(function() { - return self.reloadWithoutSaving_() - .then(function() { - return self.notifyStateChangeListeners_(); - }) - .then(fireauth.AuthUser.returnNothing_); - })); -}; - - -/** - * Refreshes the current user, if signed in. - * @return {!goog.Promise} Promise that resolves with the idToken. - * @private - */ -fireauth.AuthUser.prototype.reloadWithoutSaving_ = function() { - var self = this; - // ID token is required to refresh the user's data. - // If this is called after invalidation, getToken will throw the cached error. - return this.getIdToken().then(function(idToken) { - var isAnonymous = self['isAnonymous']; - return self.setUserAccountInfoFromToken_(idToken) - .then(function(user) { - if (!isAnonymous) { - // Preserves the not anonymous status of the stored user, - // even if no more credentials (federated or email/password) - // linked to the user. - self.updateProperty('isAnonymous', false); - } - return idToken; - }); - }); -}; - - -/** - * This operation resolves with the Firebase ID token result which contains - * the entire payload claims. - * @param {boolean=} opt_forceRefresh Whether to force refresh token exchange. - * @return {!goog.Promise} A Promise that resolves with - * the ID token result. - */ -fireauth.AuthUser.prototype.getIdTokenResult = function(opt_forceRefresh) { - return this.getIdToken(opt_forceRefresh).then(function(idToken) { - return new fireauth.IdTokenResult(idToken); - }); -}; - - -/** - * This operation resolves with the Firebase ID token. - * @param {boolean=} opt_forceRefresh Whether to force refresh token exchange. - * @return {!goog.Promise} A Promise that resolves with the ID token. - */ -fireauth.AuthUser.prototype.getIdToken = function(opt_forceRefresh) { - var self = this; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_(this.checkDestroyed_().then(function() { - return self.stsTokenManager_.getToken(opt_forceRefresh); - }).then(function(response) { - if (!response) { - // If the user exists, the token manager should be initialized. - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - // Only if the access token is refreshed, notify Auth listeners. - if (response['accessToken'] != self.lastAccessToken_) { - self.setLastAccessToken_(response['accessToken']); - // Auth state change, notify listeners. - self.notifyAuthListeners_(); - } - self.updateProperty('refreshToken', response['refreshToken']); - return response['accessToken']; - })); -}; - - -/** - * Checks if the error corresponds to a user invalidation action. - * @param {*} error The error returned by a user operation. - * @return {boolean} Whether the user is invalidated based on the error - * provided. - * @private - */ -fireauth.AuthUser.isUserInvalidated_ = function(error) { - return !!(error && - (error.code == 'auth/user-disabled' || - error.code == 'auth/user-token-expired')); -}; - - -/** - * Updates the current tokens using a server response, if new tokens are - * present and are different from the current ones, and notify the Auth - * listeners. - * @param {!Object} response The response from the server. - */ -fireauth.AuthUser.prototype.updateTokensIfPresent = function(response) { - if (response[fireauth.RpcHandler.AuthServerField.ID_TOKEN] && - this.lastAccessToken_ != response[ - fireauth.RpcHandler.AuthServerField.ID_TOKEN]) { - this.stsTokenManager_.parseServerResponse(response); - this.notifyAuthListeners_(); - this.setLastAccessToken_(response[ - fireauth.RpcHandler.AuthServerField.ID_TOKEN]); - // Update refresh token property. - this.updateProperty( - 'refreshToken', this.stsTokenManager_.getRefreshToken()); - } -}; - - -/** - * Called internally on Auth (access token) changes to notify listeners. - * @private - */ -fireauth.AuthUser.prototype.notifyAuthListeners_ = function() { - this.dispatchEvent( - new fireauth.UserEvent(fireauth.UserEventType.TOKEN_CHANGED)); -}; - - -/** - * Called internally on user deletion to notify listeners. - * @private - */ -fireauth.AuthUser.prototype.notifyUserDeletedListeners_ = function() { - this.dispatchEvent( - new fireauth.UserEvent(fireauth.UserEventType.USER_DELETED)); -}; - - -/** - * Called internally on user session invalidation to notify listeners. - * @private - */ -fireauth.AuthUser.prototype.notifyUserInvalidatedListeners_ = function() { - this.dispatchEvent( - new fireauth.UserEvent(fireauth.UserEventType.USER_INVALIDATED)); -}; - - -/** - * Queries the backend using the provided ID token for all linked accounts to - * build the Firebase user object. - * @param {string} idToken The ID token string. - * @return {!goog.Promise} - * @private - */ -fireauth.AuthUser.prototype.setUserAccountInfoFromToken_ = function (idToken) { - return this.rpcHandler_.getAccountInfoByIdToken(idToken) - .then(goog.bind(this.parseAccountInfo_, this)); -}; - - -/** - * Parses the response from the getAccountInfo endpoint. - * @param {!Object} resp The backend response. - * @private - */ -fireauth.AuthUser.prototype.parseAccountInfo_ = function(resp) { - var users = resp[fireauth.AuthUser.GET_ACCOUNT_INFO_USERS]; - if (!users || !users.length) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - var user = users[0]; - var accountInfo = /** @type {!fireauth.AuthUser.AccountInfo} */ ({ - 'uid': /** @type {string} */ ( - user[fireauth.AuthUser.GetAccountInfoField.LOCAL_ID]), - 'displayName': /** @type {?string|undefined} */ ( - user[fireauth.AuthUser.GetAccountInfoField.DISPLAY_NAME]), - 'photoURL': /** @type {?string|undefined} */ ( - user[fireauth.AuthUser.GetAccountInfoField.PHOTO_URL]), - 'email': /** @type {?string|undefined} */ ( - user[fireauth.AuthUser.GetAccountInfoField.EMAIL]), - 'emailVerified': - !!user[fireauth.AuthUser.GetAccountInfoField.EMAIL_VERIFIED], - 'phoneNumber': /** @type {?string|undefined} */ ( - user[fireauth.AuthUser.GetAccountInfoField.PHONE_NUMBER]), - 'lastLoginAt': /** @type {?string|undefined} */ ( - user[fireauth.AuthUser.GetAccountInfoField.LAST_LOGIN_AT]), - 'createdAt': /** @type {?string|undefined} */ ( - user[fireauth.AuthUser.GetAccountInfoField.CREATED_AT]), - 'tenantId': /** @type {?string|undefined} */ ( - user[fireauth.AuthUser.GetAccountInfoField.TENANT_ID]) - }); - this.setAccountInfo(accountInfo); - var linkedAccounts = this.extractLinkedAccounts_(user); - for (var i = 0; i < linkedAccounts.length; i++) { - this.addProviderData(linkedAccounts[i]); - } - // Sets the isAnonymous flag based on email, passwordHash and providerData. - var isAnonymous = !(this['email'] && - user[fireauth.AuthUser.GetAccountInfoField.PASSWORD_HASH]) && - !(this['providerData'] && this['providerData'].length); - this.updateProperty('isAnonymous', isAnonymous); - // Notify external listeners of the reload. - this.dispatchEvent(new fireauth.UserEvent( - fireauth.UserEventType.USER_RELOADED, - {userServerResponse: user})); -}; - - -/** - * Extracts the linked accounts from getAccountInfo response and returns an - * array of corresponding provider data. - * @param {!Object} resp The response object. - * @return {!Array} The linked accounts. - * @private - */ -fireauth.AuthUser.prototype.extractLinkedAccounts_ = function(resp) { - var providerInfo = - resp[fireauth.AuthUser.GetAccountInfoField.PROVIDER_USER_INFO]; - if (!providerInfo || !providerInfo.length) { - return []; - } - - return goog.array.map(providerInfo, function(info) { - return new fireauth.AuthUserInfo( - info[fireauth.AuthUser.GetAccountInfoProviderField.RAW_ID], - info[fireauth.AuthUser.GetAccountInfoProviderField.PROVIDER_ID], - info[fireauth.AuthUser.GetAccountInfoProviderField.EMAIL], - info[fireauth.AuthUser.GetAccountInfoProviderField.DISPLAY_NAME], - info[fireauth.AuthUser.GetAccountInfoProviderField.PHOTO_URL], - info[fireauth.AuthUser.GetAccountInfoProviderField.PHONE_NUMBER]); - }); -}; - - -/** - * Reauthenticates a user using a fresh credential, to be used before operations - * such as updatePassword that require tokens from recent login attempts. It - * also returns any additional user info data or credentials returned form the - * backend. It has been deprecated in favor of reauthenticateWithCredential. - * @param {!fireauth.AuthCredential} credential - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.reauthenticateAndRetrieveDataWithCredential = - function(credential) { - fireauth.deprecation.log( - fireauth.deprecation.Deprecations.REAUTH_WITH_CREDENTIAL); - return this.reauthenticateWithCredential(credential); -}; - - -/** - * Reauthenticates a user using a fresh credential, to be used before operations - * such as updatePassword that require tokens from recent login attempts. It - * also returns any additional user info data or credentials returned form the - * backend. - * @param {!fireauth.AuthCredential} credential - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.reauthenticateWithCredential = - function(credential) { - var self = this; - var userCredential = null; - // Register this pending promise but bypass user invalidation check. - return this.registerPendingPromise_( - // Match ID token from credential with the current user UID. - credential.matchIdTokenWithUid(this.rpcHandler_, this['uid']) - .then(function(response) { - // If the credential is valid and matches the current user ID, then - // update the tokens accordingly. - self.updateTokensIfPresent(response); - // Get user credential. - userCredential = self.getUserCredential_( - response, fireauth.constants.OperationType.REAUTHENTICATE); - // This could potentially validate an invalidated user. This happens in - // the case a password reset was applied. The refresh token is expired. - // Reauthentication should revalidate the user. - // User would remain non current if already signed out, but should be - // enabled again. - self.userInvalidatedError_ = null; - return self.reload(); - }).then(function() { - // Return user credential after reauthenticated user is reloaded. - return userCredential; - }), - // Skip invalidation check as reauthentication could revalidate a user. - true); -}; - - -/** - * Reloads the user and then checks if a provider is already linked. If so, - * this returns a Promise that rejects. Note that state change listeners are not - * notified on success, so that operations using this can make changes and then - * do one final listener notification. - * @param {string} providerId - * @return {!goog.Promise} - * @private - */ -fireauth.AuthUser.prototype.checkIfAlreadyLinked_ = - function(providerId) { - var self = this; - // Reload first in case the user was updated elsewhere. - return this.reloadWithoutSaving_() - .then(function() { - if (goog.array.contains(self.getProviderIds(), providerId)) { - return self.notifyStateChangeListeners_() - .then(function() { - throw new fireauth.AuthError( - fireauth.authenum.Error.PROVIDER_ALREADY_LINKED); - }); - } - }); -}; - - -/** - * Links a provider to the current user and returns any additional user info - * data or credentials returned form the backend. It has been deprecated in - * favor of linkWithCredential. - * @param {!fireauth.AuthCredential} credential The credential from the Auth - * provider. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.linkAndRetrieveDataWithCredential = - function(credential) { - fireauth.deprecation.log( - fireauth.deprecation.Deprecations.LINK_WITH_CREDENTIAL); - return this.linkWithCredential(credential); -}; - - -/** - * Links a provider to the current user and returns any additional user info - * data or credentials returned form the backend. - * @param {!fireauth.AuthCredential} credential The credential from the Auth - * provider. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.linkWithCredential = function(credential) { - var self = this; - var userCredential = null; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_( - this.checkIfAlreadyLinked_(credential['providerId']) - .then(function() { - return self.getIdToken(); - }) - .then(function(idToken) { - return credential.linkToIdToken(self.rpcHandler_, idToken); - }) - .then(function(response) { - // Get user credential. - userCredential = self.getUserCredential_( - response, fireauth.constants.OperationType.LINK); - // Finalize linking. - return self.finalizeLinking_(response); - }) - .then(function(user) { - // Return user credential after finalizing linking. - return userCredential; - }) - ); -}; - - -/** - * Links a phone number using the App verifier instance and returns a - * promise that resolves with the confirmation result which on confirmation - * will resolve with the UserCredential object. - * @param {string} phoneNumber The phone number to authenticate with. - * @param {!firebase.auth.ApplicationVerifier} appVerifier The application - * verifier. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.linkWithPhoneNumber = - function(phoneNumber, appVerifier) { - var self = this; - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_( - // Check if linked already. If so, throw an error. - // This is redundant but is needed to prevent the need to send the - // SMS (worth the cost). - this.checkIfAlreadyLinked_(fireauth.idp.ProviderId.PHONE) - .then(function() { - return fireauth.ConfirmationResult.initialize( - self.getAuth_(), - phoneNumber, - appVerifier, - // This will check again if the credential is linked. - goog.bind(self.linkWithCredential, self)); - }))); -}; - - -/** - * Reauthenticates a user with a phone number using the App verifier instance - * and returns a promise that resolves with the confirmation result which on - * confirmation will resolve with the UserCredential object. - * @param {string} phoneNumber The phone number to authenticate with. - * @param {!firebase.auth.ApplicationVerifier} appVerifier The application - * verifier. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.reauthenticateWithPhoneNumber = - function(phoneNumber, appVerifier) { - var self = this; - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_( - // Wrap this operation in a Promise since self.getAuth_() may throw an - // error synchronously. - goog.Promise.resolve().then(function() { - return fireauth.ConfirmationResult.initialize( - // Get corresponding Auth instance. - self.getAuth_(), - phoneNumber, - appVerifier, - goog.bind(self.reauthenticateWithCredential, - self)); - }), - // Skip invalidation check as reauthentication could revalidate a - // user. - true)); -}; - - -/** - * Converts an ID token response (eg. verifyAssertion) to a UserCredential - * object. - * @param {!Object} idTokenResponse The ID token response. - * @param {!fireauth.constants.OperationType} operationType The operation type - * to set in the user credential. - * @return {!fireauth.AuthEventManager.Result} The UserCredential object - * constructed from the response. - * @private - */ -fireauth.AuthUser.prototype.getUserCredential_ = - function(idTokenResponse, operationType) { - // Get credential if available in the response. - var credential = fireauth.AuthProvider.getCredentialFromResponse( - idTokenResponse); - // Get additional user info data if available in the response. - var additionalUserInfo = fireauth.AdditionalUserInfo.fromPlainObject( - idTokenResponse); - // Return the readonly copy of the user credential object. - return fireauth.object.makeReadonlyCopy({ - // Return the current user reference. - 'user': this, - // Return any credential passed from the backend. - 'credential': credential, - // Return any additional IdP data passed from the backend. - 'additionalUserInfo': additionalUserInfo, - // Return the operation type in the user credential object. - 'operationType': operationType - }); -}; - - -/** - * Finalizes a linking flow, updating idToken and user's data using the - * RPC linking response. - * @param {!Object} response The RPC linking response. - * @return {!goog.Promise} - * @private - */ -fireauth.AuthUser.prototype.finalizeLinking_ = function(response) { - // The response may contain a new access token, - // so we should update them just like a new sign in. - this.updateTokensIfPresent(response); - // This will take care of saving the updated state. - var self = this; - return this.reload().then(function() { - return self; - }); -}; - - -/** - * Updates the user's email. - * @param {string} newEmail The new email. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.updateEmail = function(newEmail) { - var self = this; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_(this.getIdToken() - .then(function(idToken) { - return self.rpcHandler_.updateEmail(idToken, newEmail); - }) - .then(function(response) { - // Calls to SetAccountInfo may invalidate old tokens. - self.updateTokensIfPresent(response); - // Reloads the user to update emailVerified. - return self.reload(); - })); -}; - - -/** - * Updates the user's phone number. - * @param {!fireauth.PhoneAuthCredential} phoneCredential - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.updatePhoneNumber = function(phoneCredential) { - var self = this; - return this.registerPendingPromise_(this.getIdToken() - .then(function(idToken) { - // The backend always overwrites the existing phone number during a - // link operation. - return phoneCredential.linkToIdToken(self.rpcHandler_, idToken); - }) - .then(function(response) { - self.updateTokensIfPresent(response); - return self.reload(); - })); -}; - - -/** - * Updates the user's password. - * @param {string} newPassword The new password. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.updatePassword = function(newPassword) { - var self = this; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_( - this.getIdToken() - .then(function(idToken) { - return self.rpcHandler_.updatePassword(idToken, newPassword); - }) - .then(function(response) { - self.updateTokensIfPresent(response); - // Reloads the user in case email has also been updated and the user - // was anonymous. - return self.reload(); - })); -}; - - -/** - * Updates the user's profile data. - * @param {!Object} profile The profile data to update. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.updateProfile = function(profile) { - if (profile['displayName'] === undefined && - profile['photoURL'] === undefined) { - // No change, directly return. - return this.checkDestroyed_(); - } - var self = this; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_( - this.getIdToken().then(function(idToken) { - // Translate the request into one that the backend accepts. - var profileRequest = { - 'displayName': profile['displayName'], - 'photoUrl': profile['photoURL'] - }; - return self.rpcHandler_.updateProfile(idToken, profileRequest); - }) - .then(function(response) { - // Calls to SetAccountInfo may invalidate old tokens. - self.updateTokensIfPresent(response); - // Update properties. - self.updateProperty('displayName', - response[fireauth.AuthUser.SetAccountInfoField.DISPLAY_NAME] || - null); - self.updateProperty('photoURL', - response[fireauth.AuthUser.SetAccountInfoField.PHOTO_URL] || null); - goog.array.forEach(self['providerData'], function(userInfo) { - // Check if password provider is linked. - if (userInfo['providerId'] === fireauth.idp.ProviderId.PASSWORD) { - // If so, update both fields in that provider. - fireauth.object.setReadonlyProperty( - userInfo, 'displayName', self['displayName']); - fireauth.object.setReadonlyProperty( - userInfo, 'photoURL', self['photoURL']); - } - }); - // Notify changes and resolve. - return self.notifyStateChangeListeners_(); - }) - .then(fireauth.AuthUser.returnNothing_)); -}; - - -/** - * Unlinks a provider from an account. - * @param {!fireauth.idp.ProviderId} providerId The ID of the provider to - * unlink. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.unlink = function(providerId) { - var self = this; - // Make sure we have updated user providers to avoid removing a linked - // provider that hasn't been updated in current copy of user. - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_( - this.reloadWithoutSaving_() - .then(function(idToken) { - // Provider already unlinked. - if (!goog.array.contains(self.getProviderIds(), providerId)) { - return self.notifyStateChangeListeners_() - .then(function() { - throw new fireauth.AuthError( - fireauth.authenum.Error.NO_SUCH_PROVIDER); - }); - } - // We delete the providerId given. - return self.rpcHandler_ - .deleteLinkedAccounts(idToken, [providerId]) - .then(function(resp) { - // Construct the set of provider IDs returned by server. - var remainingProviderIds = {}; - var userInfo = resp[fireauth.AuthUser.SetAccountInfoField. - PROVIDER_USER_INFO] || []; - goog.array.forEach(userInfo, function(obj) { - remainingProviderIds[ - obj[fireauth.AuthUser.SetAccountInfoField.PROVIDER_ID]] = - true; - }); - - // Remove all provider data objects where the provider ID no - // longer exists in this user. - goog.array.forEach(self.getProviderIds(), function(pId) { - if (!remainingProviderIds[pId]) { - // This provider no longer linked, remove it from user. - self.removeProviderData(pId); - } - }); - - // Remove the phone number if the phone provider was unlinked. - if (!remainingProviderIds[fireauth.PhoneAuthProvider[ - 'PROVIDER_ID']]) { - fireauth.object.setReadonlyProperty(self, 'phoneNumber', null); - } - - return self.notifyStateChangeListeners_(); - }); - })); -}; - - -/** - * Deletes the user, triggering an Auth token change if successful. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.delete = function() { - // Notice the way of declaring the method, it's to avoid a weird bug on IE8. - var self = this; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_( - this.getIdToken() - .then(function(idToken) { - return self.rpcHandler_.deleteAccount(idToken); - }) - .then(function() { - self.notifyUserDeletedListeners_(); - })) - .then(function() { - // Destroying after the registered promise is handled ensures it won't - // be canceled. - self.destroy(); - }); -}; - - -/** - * Tells the Auth event manager if this user is the owner of a detected Auth - * event. A user can handle linkWithPopup and linkWithRedirect operations. - * In addition, the event ID should match the user's event IDs. - * @param {!fireauth.AuthEvent.Type} mode The Auth operation mode (popup, - * redirect). - * @param {?string=} opt_eventId The event ID. - * @return {boolean} Whether the Auth event handler can handler the provided - * event. - * @override - */ -fireauth.AuthUser.prototype.canHandleAuthEvent = function(mode, opt_eventId) { - if (mode == fireauth.AuthEvent.Type.LINK_VIA_POPUP && - this.getPopupEventId() == opt_eventId && - this.pendingPopupResolvePromise_) { - // The link via popup event's ID matches the user's popup event ID which - // makes this user the owner of this event. - return true; - } else if (mode == fireauth.AuthEvent.Type.REAUTH_VIA_POPUP && - this.getPopupEventId() == opt_eventId && - this.pendingPopupResolvePromise_) { - // The reauth via popup event's ID matches the user's popup event ID which - // makes this user the owner of this event. - return true; - } else if (mode == fireauth.AuthEvent.Type.LINK_VIA_REDIRECT && - this.getRedirectEventId() == opt_eventId) { - // The link via redirect event's ID matches the user's redirect event ID - // which makes this user the owner of this event. - return true; - } else if (mode == fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT && - this.getRedirectEventId() == opt_eventId) { - // The reauth via redirect event's ID matches the user's redirect event ID - // which makes this user the owner of this event. - return true; - } - return false; -}; - - -/** - * Completes the pending popup operation. If error is not null, rejects with the - * error. Otherwise, it resolves with the popup redirect result. - * @param {!fireauth.AuthEvent.Type} mode The Auth operation mode (popup, - * redirect). - * @param {?fireauth.AuthEventManager.Result} popupRedirectResult The result - * to resolve with when no error supplied. - * @param {?fireauth.AuthError} error When supplied, the promise will reject. - * @param {?string=} opt_eventId The event ID. - * @override - */ -fireauth.AuthUser.prototype.resolvePendingPopupEvent = - function(mode, popupRedirectResult, error, opt_eventId) { - // Only handles popup events with event IDs that match a pending popup ID. - if ((mode != fireauth.AuthEvent.Type.LINK_VIA_POPUP && - mode != fireauth.AuthEvent.Type.REAUTH_VIA_POPUP) || - opt_eventId != this.getPopupEventId()) { - return; - } - if (error && this.pendingPopupRejectPromise_) { - // Reject with error for supplied mode. - this.pendingPopupRejectPromise_(error); - } else if (popupRedirectResult && - !error && - this.pendingPopupResolvePromise_) { - // Resolve with result for supplied mode. - this.pendingPopupResolvePromise_(popupRedirectResult); - } - // Now that event is resolved, delete timeout promise. - if (this.popupTimeoutPromise_) { - this.popupTimeoutPromise_.cancel(); - this.popupTimeoutPromise_ = null; - } - // Delete pending promises. - delete this.pendingPopupResolvePromise_; - delete this.pendingPopupRejectPromise_; -}; - - -/** - * Returns the handler's appropriate popup and redirect sign in operation - * finisher. Can handle link or reauth events that match existing event IDs. - * @param {!fireauth.AuthEvent.Type} mode The Auth operation mode (popup, - * redirect). - * @param {?string=} opt_eventId The optional event ID. - * @return {?function(string, string, ?string, - * ?string=):!goog.Promise} - * @override - */ -fireauth.AuthUser.prototype.getAuthEventHandlerFinisher = - function(mode, opt_eventId) { - if (mode == fireauth.AuthEvent.Type.LINK_VIA_POPUP && - opt_eventId == this.getPopupEventId()) { - // Link with popup ID matches popup event ID. - return goog.bind(this.finishPopupAndRedirectLink, this); - } else if (mode == fireauth.AuthEvent.Type.REAUTH_VIA_POPUP && - opt_eventId == this.getPopupEventId()) { - // Reauth with popup ID matches popup event ID. - return goog.bind(this.finishPopupAndRedirectReauth, this); - } else if (mode == fireauth.AuthEvent.Type.LINK_VIA_REDIRECT && - this.getRedirectEventId() == opt_eventId) { - // Link with redirect ID matches redirect event ID. - return goog.bind(this.finishPopupAndRedirectLink, this); - } else if (mode == fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT && - this.getRedirectEventId() == opt_eventId) { - // Reauth with redirect ID matches redirect event ID. - return goog.bind(this.finishPopupAndRedirectReauth, this); - } - return null; -}; - - -/** - * @return {string} The generated event ID used to identify a popup or redirect - * event. - * @private - */ -fireauth.AuthUser.prototype.generateEventId_ = function() { - return fireauth.util.generateEventId(this['uid'] + ':::'); -}; - - -/** - * Links to Auth provider via popup. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.linkWithPopup = function(provider) { - var self = this; - // Additional check to return to fail when the provider is already linked. - var additionalCheck = function() { - return self.checkIfAlreadyLinked_(provider['providerId']) - .then(function() { - // Notify state listeners after the check as it will update the user - // state. - return self.notifyStateChangeListeners_(); - }); - }; - return this.runOperationWithPopup_( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, provider, additionalCheck, false); -}; - - -/** - * Reauthenticate to Auth provider via popup. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.reauthenticateWithPopup = function(provider) { - // No additional check needed before running this operation. - var additionalCheck = function() { - return goog.Promise.resolve(); - }; - return this.runOperationWithPopup_( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - provider, - additionalCheck, - // Do not update token and skip session invalidation check. - true); -}; - - -/** - * Runs a specific OAuth operation using the Auth provider via popup. - * @param {!fireauth.AuthEvent.Type} mode The mode of operation (link or - * reauth). - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {function():!goog.Promise} additionalCheck The additional check to - * run before proceeding with the popup processing. - * @param {boolean} isReauthOperation whether this is a reauth operation or not. - * @return {!goog.Promise} - * @private - */ -fireauth.AuthUser.prototype.runOperationWithPopup_ = - function(mode, provider, additionalCheck, isReauthOperation) { - // Check if popup and redirect are supported in this environment. - if (!fireauth.util.isPopupRedirectSupported()) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - } - // Quickly throw user invalidation error if already invalidated. - if (this.userInvalidatedError_ && - // Skip invalidation check as reauthentication could revalidate a user. - !isReauthOperation) { - return goog.Promise.reject(this.userInvalidatedError_); - } - var self = this; - // Popup the window immediately to make sure the browser associates the - // popup with the click that triggered it. - - // Get provider settings. - var settings = fireauth.idp.getIdpSettings(provider['providerId']); - // There could multiple users at the same time and multiple users could have - // the same UID. So try to ensure event ID uniqueness. - var eventId = this.generateEventId_(); - // If incapable of redirecting popup from opener, popup destination URL - // directly. This could also happen in a sandboxed iframe. - var oauthHelperWidgetUrl = null; - if ((!fireauth.util.runsInBackground() || fireauth.util.isIframe()) && - this.authDomain_ && - provider['isOAuthProvider']) { - oauthHelperWidgetUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - this.authDomain_, - this.apiKey_, - this.appName_, - mode, - provider, - null, - eventId, - firebase.SDK_VERSION || null, - null, - null, - this['tenantId'], - this.emulatorConfig_); - } - // The popup must have a name, otherwise when successive popups are triggered - // they will all render in the same instance and none will succeed since the - // popup cancel of first window will close the shared popup window instance. - var popupWin = - fireauth.util.popup( - oauthHelperWidgetUrl, - fireauth.util.generateRandomString(), - settings && settings.popupWidth, - settings && settings.popupHeight); - var p = additionalCheck().then(function() { - // Auth event manager must be available for account linking or - // reauthentication to be possible. - self.getAuthEventManager(); - if (!isReauthOperation) { - // Some operations like reauthenticate do not require this. - return self.getIdToken().then(function(idToken) {}); - } - }).then(function() { - // Process popup request. - return self.authEventManager_.processPopup( - popupWin, mode, provider, eventId, !!oauthHelperWidgetUrl, - self['tenantId']); - }).then(function() { - return new goog.Promise(function(resolve, reject) { - // Expire other pending promises if still available. - self.resolvePendingPopupEvent( - mode, - null, - new fireauth.AuthError(fireauth.authenum.Error.EXPIRED_POPUP_REQUEST), - // Existing popup event ID. - self.getPopupEventId()); - // Save current pending promises. - self.pendingPopupResolvePromise_ = resolve; - self.pendingPopupRejectPromise_ = reject; - // Overwrite popup event ID with new one. - self.setPopupEventId(eventId); - // Keep track of timeout promise to cancel it on promise resolution before - // it times out. - self.popupTimeoutPromise_ = - self.authEventManager_.startPopupTimeout( - self, mode, /** @type {!Window} */ (popupWin), eventId); - }); - }).then(function(result) { - // On resolution, close popup if still opened and pass result through. - if (popupWin) { - fireauth.util.closeWindow(popupWin); - } - if (result) { - return fireauth.object.makeReadonlyCopy(result); - } - return null; - }).thenCatch(function(error) { - if (popupWin) { - fireauth.util.closeWindow(popupWin); - } - throw error; - }); - // Register this pending promise. This will also check for user invalidation. - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_( - p, - // Skip invalidation check as reauthentication could revalidate a - // user. - isReauthOperation)); -}; - - -/** - * Links to Auth provider via redirect. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.linkWithRedirect = function(provider) { - var mode = fireauth.AuthEvent.Type.LINK_VIA_REDIRECT; - var self = this; - // Additional check to return to fail when the provider is already linked. - var additionalCheck = function() { - return self.checkIfAlreadyLinked_(provider['providerId']); - }; - return this.runOperationWithRedirect_(mode, provider, additionalCheck, false); -}; - - -/** - * Reauthenticates to Auth provider via redirect. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.reauthenticateWithRedirect = function(provider) { - // No additional check needed. - var additionalCheck = function() { - return goog.Promise.resolve(); - }; - return this.runOperationWithRedirect_( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - provider, - additionalCheck, - // Do not update token and skip session invalidation check. - true); -}; - - - -/** - * Runs a specific OAuth operation using the Auth provider via redirect. - * @param {!fireauth.AuthEvent.Type} mode The mode of operation (link or - * reauth). - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {function():!goog.Promise} additionalCheck The additional check to - * run before proceeding with the redirect processing. - * @param {boolean} isReauthOperation whether this is a reauth operation or not. - * @return {!goog.Promise} - * @private - */ -fireauth.AuthUser.prototype.runOperationWithRedirect_ = - function(mode, provider, additionalCheck, isReauthOperation) { - // Check if popup and redirect are supported in this environment. - if (!fireauth.util.isPopupRedirectSupported()) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - } - // Quickly throw user invalidation error if already invalidated. - if (this.userInvalidatedError_ && - // Skip invalidation check as reauthentication could revalidate a user. - !isReauthOperation) { - return goog.Promise.reject(this.userInvalidatedError_); - } - var self = this; - var errorThrown = null; - // There could multiple users at the same time and multiple users could have - // the same UID. So try to ensure event ID uniqueness. - var eventId = this.generateEventId_(); - var p = additionalCheck().then(function() { - // Auth event manager must be available for account linking or - // reauthentication to be possible. - self.getAuthEventManager(); - if (!isReauthOperation) { - // Some operations like reauthenticate do not require this. - return self.getIdToken().then(function(idToken) {}); - } - }).then(function() { - // Process redirect operation. - self.setRedirectEventId(eventId); - // Before redirecting save the event ID. - // It is important that the user redirect event ID is updated in storage - // before redirecting. - return self.notifyStateChangeListeners_(); - }).then(function(user) { - if (self.redirectStorageManager_) { - // Save the user before redirecting in case it is not current so that it - // can be retrieved after reloading for linking or reauthentication to - // succeed. - return self.redirectStorageManager_.setRedirectUser(self); - } - return user; - }).then(function(user) { - // Complete the redirect operation. - return self.authEventManager_.processRedirect( - mode, provider, eventId, self['tenantId']); - }).thenCatch(function(error) { - // Catch error if any is generated. - errorThrown = error; - if (self.redirectStorageManager_) { - // If an error is detected, delete the redirected user from storage. - return self.redirectStorageManager_.removeRedirectUser(); - } - // No storage manager, just throw error. - throw errorThrown; - }).then(function() { - // Rethrow the error. - if (errorThrown) { - throw errorThrown; - } - }); - // Register this pending promise. This will also check for user invalidation. - return /** @type {!goog.Promise} */ (this.registerPendingPromise_( - p, - // Skip invalidation check as reauthentication could revalidate a user. - isReauthOperation)); -}; - - -/** - * @return {!fireauth.AuthEventManager} The user's Auth event manager. - */ -fireauth.AuthUser.prototype.getAuthEventManager = function() { - // Either return the manager instance if available, otherwise throw an error. - if (this.authEventManager_ && this.popupRedirectEnabled_) { - return this.authEventManager_; - } else if (this.authEventManager_ && !this.popupRedirectEnabled_) { - // This should not happen as Auth will enable a user after it is created. - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_AUTH_DOMAIN); -}; - - -/** - * Finishes the popup and redirect account linking operations. - * @param {string} requestUri The callback URL with the OAuth response. - * @param {string} sessionId The session ID used to generate the authUri. - * @param {?string} tenantId The tenant ID. - * @param {?string=} opt_postBody The optional POST body content. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.finishPopupAndRedirectLink = - function(requestUri, sessionId, tenantId, opt_postBody) { - var self = this; - // Now that popup has responded, delete popup timeout promise. - if (this.popupTimeoutPromise_) { - this.popupTimeoutPromise_.cancel(); - this.popupTimeoutPromise_ = null; - } - var userCredential = null; - // This routine could be run before init state, make sure it waits for that to - // complete otherwise this would fail as user not loaded from storage yet. - var p = this.getIdToken() - .then(function(idToken) { - var request = { - 'requestUri': requestUri, - 'postBody': opt_postBody, - 'sessionId': sessionId, - // To link a tenant user, the tenant ID will be passed to the - // backend as part of the ID token. - 'idToken': idToken - }; - // This operation should fail if new ID token differs from old one. - // So this can be treate as a profile update operation. - return self.rpcHandler_.verifyAssertionForLinking(request); - }) - .then(function(response) { - // Get user credential. - userCredential = self.getUserCredential_( - response, fireauth.constants.OperationType.LINK); - // Finalizes the linking process. - return self.finalizeLinking_(response); - }) - .then(function(user) { - // Return the user credential response. - return userCredential; - }); - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_(p)); -}; - - -/** - * Finishes the popup and redirect account reauthentication operations. - * @param {string} requestUri The callback URL with the OAuth response. - * @param {string} sessionId The session ID used to generate the authUri. - * @param {?string} tenantId The tenant ID. - * @param {?string=} opt_postBody The optional POST body content. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.finishPopupAndRedirectReauth = - function(requestUri, sessionId, tenantId, opt_postBody) { - var self = this; - // Now that popup has responded, delete popup timeout promise. - if (this.popupTimeoutPromise_) { - this.popupTimeoutPromise_.cancel(); - this.popupTimeoutPromise_ = null; - } - var userCredential = null; - // This routine could be run before init state, make sure it waits for that to - // complete otherwise this would fail as user not loaded from storage yet. - var p = goog.Promise.resolve() - .then(function() { - var request = { - 'requestUri': requestUri, - 'sessionId': sessionId, - 'postBody': opt_postBody, - // To reauthenticate a tenant user, the tenant ID will be passed to - // the backend explicitly. - // Even if tenant ID is null, still pass it to RPC handler explicitly - // so that it won't be overridden by RPC handler's tenant ID. - 'tenantId': tenantId - }; - // Finish sign in by calling verifyAssertionForExisting and then - // matching the returned ID token's UID with the current user's. - return fireauth.AuthCredential.verifyTokenResponseUid( - self.rpcHandler_.verifyAssertionForExisting(request), - self['uid']); - }).then(function(response) { - // Get credential from response if available. - // Get user credential. - userCredential = self.getUserCredential_( - response, fireauth.constants.OperationType.REAUTHENTICATE); - // If the credential is valid and matches the current user ID, then - // update the tokens accordingly. - self.updateTokensIfPresent(response); - // This could potentially validate an invalidated user. This happens in - // the case a password reset was applied. The refresh token is expired. - // Reauthentication should revalidate the user. - // User would remain non current if already signed out, but should be - // enabled again. - self.userInvalidatedError_ = null; - return self.reload(); - }) - .then(function() { - // Return the user credential response. - return userCredential; - }); - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_( - p, - // Skip invalidation check as reauthentication could revalidate a - // user. - true)); -}; - - -/** - * Sends the verification email to the email in the user's account. - * @param {?Object=} opt_actionCodeSettings The optional action code settings - * object. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.sendEmailVerification = - function(opt_actionCodeSettings) { - var self = this; - var idToken = null; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_( - // Wrap in promise as ActionCodeSettings constructor could throw a - // synchronous error if invalid arguments are specified. - this.getIdToken().then(function(latestIdToken) { - idToken = latestIdToken; - if (typeof opt_actionCodeSettings !== 'undefined' && - // Ignore empty objects. - !goog.object.isEmpty(opt_actionCodeSettings)) { - return new fireauth.ActionCodeSettings( - /** @type {!Object} */ (opt_actionCodeSettings)).buildRequest(); - } - return {}; - }) - .then(function(additionalRequestData) { - return self.rpcHandler_.sendEmailVerification( - /** @type {string} */ (idToken), additionalRequestData); - }) - .then(function(email) { - if (self['email'] != email) { - // Our local copy does not have an email. If the email changed, - // reload the user. - return self.reload(); - } - }) - .then(function() { - // Return nothing. - })); -}; - - -/** - * Sends the verification email before updating the email on the user. - * @param {string} newEmail The new email. - * @param {?Object=} opt_actionCodeSettings The optional action code settings - * object. - * @return {!goog.Promise} - */ -fireauth.AuthUser.prototype.verifyBeforeUpdateEmail = - function(newEmail, opt_actionCodeSettings) { - var self = this; - var idToken = null; - // Register this pending promise. This will also check for user invalidation. - return this.registerPendingPromise_( - // Wrap in promise as ActionCodeSettings constructor could throw a - // synchronous error if invalid arguments are specified. - this.getIdToken().then(function(latestIdToken) { - idToken = latestIdToken; - if (typeof opt_actionCodeSettings !== 'undefined' && - // Ignore empty objects. - !goog.object.isEmpty(opt_actionCodeSettings)) { - return new fireauth.ActionCodeSettings( - /** @type {!Object} */ (opt_actionCodeSettings)).buildRequest(); - } - return {}; - }) - .then(function(additionalRequestData) { - return self.rpcHandler_.verifyBeforeUpdateEmail( - /** @type {string} */ (idToken), newEmail, additionalRequestData); - }) - .then(function(email) { - if (self['email'] != email) { - // If the local copy of the email on user is outdated, reload the - // user. - return self.reload(); - } - }) - .then(function() { - // Return nothing. - })); -}; - - -/** - * Destroys the user object and makes further operations invalid. Sensitive - * fields (refreshToken) are also cleared. - */ -fireauth.AuthUser.prototype.destroy = function() { - // Cancel all pending promises. - for (var i = 0; i < this.pendingPromises_.length; i++) { - this.pendingPromises_[i].cancel(fireauth.authenum.Error.MODULE_DESTROYED); - } - // Stop listening to language code changes. - this.setLanguageCodeChangeDispatcher(null); - // Stop listening to emulator config changes. - this.setEmulatorConfigChangeDispatcher(null); - // Stop listening to framework changes. - this.setFrameworkChangeDispatcher(null); - // Empty pending promises array. - this.pendingPromises_ = []; - this.destroyed_ = true; - // Stop proactive refresh if running. - this.stopProactiveRefresh(); - fireauth.object.setReadonlyProperty(this, 'refreshToken', null); - // Make sure the destroyed user is unsubscribed from Auth event handling. - if (this.authEventManager_) { - this.authEventManager_.unsubscribe(this); - } -}; - - -/** - * Takes in a pending promise, saves it and adds a clean up callback which - * forgets the pending promise after it is fulfilled and echoes the promise - * back. If in the process, a user invalidation error is detected, caches the - * error so next time a call is made on the user, the operation will fail with - * the cached error. - * @param {!goog.Promise<*, *>|!goog.Promise} p The pending promise. - * @param {boolean=} opt_skipInvalidationCheck Whether to skip invalidation - * check. - * @return {!goog.Promise<*, *>|!goog.Promise} - * @private - */ -fireauth.AuthUser.prototype.registerPendingPromise_ = - function(p, opt_skipInvalidationCheck) { - var self = this; - // Check if user invalidation occurs. - var processedP = this.checkIfInvalidated_(p, opt_skipInvalidationCheck); - // Save created promise in pending list. - this.pendingPromises_.push(processedP); - processedP.thenAlways(function() { - // When fulfilled, remove from pending list. - goog.array.remove(self.pendingPromises_, processedP); - }); - // Return the promise. - return processedP - .thenCatch(function(error) { - var multiFactorError = null; - if (error && error['code'] === 'auth/multi-factor-auth-required') { - multiFactorError = fireauth.MultiFactorError.fromPlainObject( - error.toPlainObject(), - self.getAuth_(), - goog.bind(self.handleMultiFactorIdTokenResolver_, self)); - } - throw multiFactorError || error; - }); -}; - - -/** - * Completes multi-factor sign-in. This is only relevant for re-authentication - * flows. - * @param {{idToken: string, refreshToken: string}} response The successful - * sign-in response containing the new ID tokens. - * @return {!goog.Promise} A Promise that - * resolves with the updated `UserCredential`. - * @private - */ -fireauth.AuthUser.prototype.handleMultiFactorIdTokenResolver_ = - function(response) { - var userCredential = null; - var self = this; - // Validate token response matches current user ID. - var p = fireauth.AuthCredential.verifyTokenResponseUid( - goog.Promise.resolve(response), - self['uid']) - .then(function(response) { - // Get credential from response if available. - userCredential = self.getUserCredential_( - response, fireauth.constants.OperationType.REAUTHENTICATE); - // If the credential is valid and matches the current user ID, then - // update the tokens accordingly. - self.updateTokensIfPresent(response); - // This could potentially validate an invalidated user. - self.userInvalidatedError_ = null; - // Reload the user with the latest profile. - return self.reload(); - }) - .then(function() { - // Return the user credential response. - return userCredential; - }); - return /** @type {!goog.Promise} */ ( - this.registerPendingPromise_( - p, - // Skip invalidation check as reauthentication could revalidate a - // user. - true)); -}; - - -/** - * Check if user invalidation occurs. If so, it caches the error so it can be - * thrown immediately the next time an operation is run on the user. - * @param {!goog.Promise<*, *>|!goog.Promise} p The pending promise. - * @param {boolean=} opt_skipInvalidationCheck Whether to skip invalidation - * check. - * @return {!goog.Promise<*, *>|!goog.Promise} - * @private - */ -fireauth.AuthUser.prototype.checkIfInvalidated_ = - function(p, opt_skipInvalidationCheck) { - var self = this; - // Already invalidated, reject with token expired error. - // Unless invalidation check is to be skipped. - if (this.userInvalidatedError_ && !opt_skipInvalidationCheck) { - // Cancel pending promise. - p.cancel(); - // Reject with cached error. - return goog.Promise.reject(this.userInvalidatedError_); - } - return p.thenCatch(function(error) { - // Session invalidated. - if (fireauth.AuthUser.isUserInvalidated_(error)) { - // Notify listeners of invalidated session. - if (!self.userInvalidatedError_) { - self.notifyUserInvalidatedListeners_(); - } - // Cache the invalidation error. - self.userInvalidatedError_ = /** @type {!fireauth.AuthError} */ (error); - } - // Rethrow the error. - throw error; - }); -}; - - -/** - * @return {!Object} The object representation of the user instance. - * @override - */ -fireauth.AuthUser.prototype.toJSON = function() { - // Return the plain object representation in case JSON.stringify is called on - // a user instance. - return this.toPlainObject(); -}; - - -/** - * @return {!Object} The object representation of the user instance. - */ -fireauth.AuthUser.prototype.toPlainObject = function() { - var obj = { - 'uid': this['uid'], - 'displayName': this['displayName'], - 'photoURL': this['photoURL'], - 'email': this['email'], - 'emailVerified': this['emailVerified'], - 'phoneNumber': this['phoneNumber'], - 'isAnonymous': this['isAnonymous'], - 'tenantId': this['tenantId'], - 'providerData': [], - 'apiKey': this.apiKey_, - 'appName': this.appName_, - 'authDomain': this.authDomain_, - 'stsTokenManager': this.stsTokenManager_.toPlainObject(), - // Redirect event ID must be maintained in case there is a pending redirect - // event. - 'redirectEventId': this.getRedirectEventId() - }; - // Extend user plain object with metadata object. - if (this['metadata']) { - goog.object.extend(obj, this['metadata'].toPlainObject()); - } - goog.array.forEach(this['providerData'], function(userInfo) { - obj['providerData'].push(fireauth.object.makeWritableCopy(userInfo)); - }); - // Extend plain object with multi-factor user data. - goog.object.extend(obj, this.multiFactorUser_.toPlainObject()); - return obj; -}; - - -/** - * Converts a plain user object to {@code fireauth.AuthUser}. - * @param {!Object} user The object representation of the user instance. - * @return {?fireauth.AuthUser} The Firebase user object corresponding to - * object. - */ -fireauth.AuthUser.fromPlainObject = function(user) { - if (!user['apiKey']) { - return null; - } - var options = { - 'apiKey': user['apiKey'], - 'authDomain': user['authDomain'], - 'appName': user['appName'], - 'emulatorConfig': user['emulatorConfig'] - }; - // Convert to server response format. Constructor does not take - // stsTokenManager toPlainObject as that format is different than the return - // server response which is always used to initialize a user instance. - var stsTokenManagerResponse = {}; - if (user['stsTokenManager'] && - user['stsTokenManager']['accessToken']) { - stsTokenManagerResponse[fireauth.RpcHandler.AuthServerField.ID_TOKEN] = - user['stsTokenManager']['accessToken']; - // Refresh token could be expired. - stsTokenManagerResponse[fireauth.RpcHandler.AuthServerField.REFRESH_TOKEN] = - user['stsTokenManager']['refreshToken'] || null; - const expirationTime = user['stsTokenManager']['expirationTime']; - if (expirationTime) { - stsTokenManagerResponse[fireauth.RpcHandler.AuthServerField - .EXPIRES_IN] = - (expirationTime - Date.now()) / 1000; - } - } else { - // Token response is a required field. - return null; - } - var firebaseUser = new fireauth.AuthUser(options, - stsTokenManagerResponse, - /** @type {!fireauth.AuthUser.AccountInfo} */ (user)); - if (user['providerData']) { - goog.array.forEach(user['providerData'], function(userInfo) { - if (userInfo) { - firebaseUser.addProviderData(/** @type {!fireauth.AuthUserInfo} */ ( - fireauth.object.makeReadonlyCopy(userInfo))); - } - }); - } - // Redirect event ID must be restored to complete any pending link with - // redirect operation owned by this user. - if (user['redirectEventId']) { - firebaseUser.setRedirectEventId(user['redirectEventId']); - } - return firebaseUser; -}; - - - -/** - * Factory method for initializing a Firebase user object and populating its - * user info. This is the recommended way for initializing a user externally. - * On sign in/up operation, the server returns a token response. The response is - * all that is needed to initialize this user. - * @param {!Object} appOptions The application options. - * @param {!Object} stsTokenResponse The server STS token response. - * @param {?fireauth.storage.RedirectUserManager=} - * opt_redirectStorageManager The utility used to store and delete a user on - * link with redirect. - * @param {?Array=} opt_frameworks The list of frameworks to log on the - * user on initialization. - * @return {!goog.Promise} - */ -fireauth.AuthUser.initializeFromIdTokenResponse = function(appOptions, - stsTokenResponse, opt_redirectStorageManager, opt_frameworks) { - // Initialize the Firebase Auth user. - var user = new fireauth.AuthUser( - appOptions, stsTokenResponse); - // If redirect storage manager provided, set it. - if (opt_redirectStorageManager) { - user.setRedirectStorageManager(opt_redirectStorageManager); - } - // If frameworks provided, set it. - if (opt_frameworks) { - user.setFramework(opt_frameworks); - } - // Updates the user info and data and resolves with a user instance. - return user.reload().then(function() { - return user; - }); -}; - - -/** - * Returns an AuthUser copy of the provided user using the provided parameters - * without making any network request. - * @param {!fireauth.AuthUser} user The user to be copied. - * @param {?Object=} opt_appOptions The application options. - * @param {?fireauth.storage.RedirectUserManager=} - * opt_redirectStorageManager The utility used to store and delete a user on - * link with redirect. - * @param {?Array=} opt_frameworks The list of frameworks to log on the - * user on initialization. - * @return {!fireauth.AuthUser} - */ -fireauth.AuthUser.copyUser = function(user, opt_appOptions, - opt_redirectStorageManager, opt_frameworks) { - var appOptions = opt_appOptions || { - 'apiKey': user.apiKey_, - 'authDomain': user.authDomain_, - 'appName': user.appName_ - }; - var newUser = new fireauth.AuthUser( - appOptions, user.getStsTokenManager().toServerResponse()); - // If redirect storage manager provided, set it. - if (opt_redirectStorageManager) { - newUser.setRedirectStorageManager(opt_redirectStorageManager); - } - // If frameworks provided, set it. - if (opt_frameworks) { - newUser.setFramework(opt_frameworks); - } - // Copy remaining properties. - newUser.copy(user); - return newUser; -}; diff --git a/packages/auth/src/cacherequest.js b/packages/auth/src/cacherequest.js deleted file mode 100644 index 61eae259471..00000000000 --- a/packages/auth/src/cacherequest.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Utility for caching requests that return promises, typically - * used for requests that are sent to the server. - */ - -goog.provide('fireauth.CacheRequest'); - -goog.require('goog.Promise'); - - -/** - * This utility caches a function call that returns a promise along with its - * arguments. It gives the user the option to specify the cache duration and - * whether to cach errors as well as the ability to purge cache with its - * contents if needed. - * @constructor @struct - */ -fireauth.CacheRequest = function() { - /** @private {?function(*):!goog.Promise} The function to cache. */ - this.func_ = null; - /** @private {*} The context (this) of the function to cache. */ - this.self_ = null; - /** @private {Array} The array of arguments to run the function with. */ - this.arguments_ = []; - /** @private {?goog.Promise} The cached returned promise result. */ - this.cachedResult_ = null; - /** @private {number} The expiration timestamp of the cached result. */ - this.expirationTime_ = Date.now(); - /** @private {number} The time to live from the caching point in time. */ - this.ttl_ = 0; - /** @private {boolean} Whether to cache errors too. */ - this.cacheErrors_ = false; -}; - - -/** - * @param {function(*):!goog.Promise} func The function to cache. - * @param {*} self The context (this) of the function to cache. - * @param {Array} args The array of arguments to run the function with. - * @param {number} ttl The time to live for any cached results in milliseconds. - * @param {boolean=} opt_cacheErrors Whether to cache errors too. - */ -fireauth.CacheRequest.prototype.cache = - function(func, self, args, ttl, opt_cacheErrors) { - this.func_ = func; - this.self_ = self; - this.arguments_ = args; - this.expirationTime_ = Date.now(); - this.ttl_ = ttl; - this.cacheErrors_ = !!opt_cacheErrors; - -}; - - -/** - * @return {!goog.Promise} The promise that resolves when the function is run - * or the previously cached promise. - */ -fireauth.CacheRequest.prototype.run = function() { - var self = this; - if (!this.func_) { - throw new Error('No available configuration cached!'); - } - // If the result is not cached or the cache result is outdated. - if (!this.cachedResult_ || Date.now() >= this.expirationTime_) { - // Set expiration of current request. - this.expirationTime_ = Date.now() + this.ttl_; - // Get new result and cache it. - this.cachedResult_ = - this.func_.apply(this.self_, this.arguments_).then(function(result) { - // When successful resolution, just return the result which is to be - // cached. - return result; - }).thenCatch(function(error) { - // When an error is thrown. - if (!self.cacheErrors_) { - // Do not cache errors if errors are not to be cached. - // This will bust the cached result. Otherwise the error is cached. - self.expirationTime_ = Date.now(); - } - // Throw the returned error. - throw error; - }); - } - // Return the cached result. - return this.cachedResult_; -}; - - -/** Purges any cached results. */ -fireauth.CacheRequest.prototype.purge = function() { - // Purge the cached results. - this.cachedResult_ = null; - this.expirationTime_ = Date.now(); -}; diff --git a/packages/auth/src/confirmationresult.js b/packages/auth/src/confirmationresult.js deleted file mode 100644 index 7ae05c0c527..00000000000 --- a/packages/auth/src/confirmationresult.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the firebase.auth.ConfirmationResult. This is needed - * to provide first class support for phone Auth API: signInWithPhoneNumber, - * linkWithPhoneNumber and reauthenticateWithPhoneNumber. - */ - -goog.provide('fireauth.ConfirmationResult'); - -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.object'); -goog.require('goog.Promise'); - - -/** - * The confirmation result class. This takes in the verification ID returned - * from the phone Auth provider and the credential resolver to run when - * confirming with a verification code. - * @param {string} verificationId The verification ID returned from the Phone - * Auth provider after sending the verification code. - * @param {!function(!fireauth.AuthCredential): - * !goog.Promise} credentialResolver a - * function that takes in an AuthCredential and returns a promise that - * resolves with a UserCredential object. - * @constructor - */ -fireauth.ConfirmationResult = function(verificationId, credentialResolver) { - /** - * @const @private {!function(!fireauth.AuthCredential): - * !goog.Promise} A function that takes - * in an AuthCredential and returns a promise that resolves with a - * UserCredential object. - */ - this.credentialResolver_ = credentialResolver; - // Set verificationId as read-only property. - fireauth.object.setReadonlyProperty(this, 'verificationId', verificationId); -}; - - -/** - * Confirms the verification code and returns a promise that resolves with the - * User Credential object. - * @param {string} verificationCode The phone Auth verification code to use to - * complete the Auth flow. - * @return {!goog.Promise} - */ -fireauth.ConfirmationResult.prototype.confirm = function(verificationCode) { - // Initialize a phone Auth credential with the verification ID and code. - var credential = fireauth.PhoneAuthProvider.credential( - this['verificationId'], verificationCode); - // Run the credential resolver with the phone Auth credential and return its - // result. - return this.credentialResolver_(credential); -}; - - -/** - * Initializes a ConfirmationResult using the provided phone number, app - * verifier and returns it asynchronously. On code confirmation, the result will - * resolve using the credential resolver provided. - * @param {!fireauth.Auth} auth The corresponding Auth instance. - * @param {string} phoneNumber The phone number to authenticate with. - * @param {!firebase.auth.ApplicationVerifier} appVerifier The application - * verifier. - * @param {!function(!fireauth.AuthCredential): - * !goog.Promise} credentialResolver a - * function that takes in an AuthCredential and returns a promise that - * resolves with a UserCredential object. - * @return {!goog.Promise} - */ -fireauth.ConfirmationResult.initialize = - function(auth, phoneNumber, appVerifier, credentialResolver) { - // Initialize a phone Auth provider instance using the provided Auth - // instance. - var phoneAuthProvider = new fireauth.PhoneAuthProvider(auth); - // Verify the phone number. - return phoneAuthProvider.verifyPhoneNumber(phoneNumber, appVerifier) - .then(function(verificationId) { - // When code is sent and verification ID is returned, initialize a - // ConfirmationResult with the returned verification ID and credential - // resolver, and return that instance. - return new fireauth.ConfirmationResult( - verificationId, credentialResolver); - }); -}; diff --git a/packages/auth/src/cordovahandler.js b/packages/auth/src/cordovahandler.js deleted file mode 100644 index 20e09ba75fa..00000000000 --- a/packages/auth/src/cordovahandler.js +++ /dev/null @@ -1,870 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines Cordova utility and helper functions. - * The following plugins must be installed: - * cordova plugin add cordova-plugin-buildinfo - * cordova plugin add cordova-universal-links-plugin-fix - * cordova plugin add cordova-plugin-browsertab - * cordova plugin add cordova-plugin-inappbrowser - * iOS custom scheme support: - * cordova plugin add cordova-plugin-customurlscheme --variable \ - * URL_SCHEME=com.firebase.example - * Console logging in iOS: - * cordova plugin add cordova-plugin-console - */ - -goog.provide('fireauth.CordovaHandler'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.AuthProvider'); -goog.require('fireauth.DynamicLink'); -goog.require('fireauth.OAuthSignInHandler'); -goog.require('fireauth.UniversalLinkSubscriber'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.storage.AuthEventManager'); -goog.require('fireauth.storage.OAuthHandlerManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Timer'); -goog.require('goog.Uri'); -goog.require('goog.array'); -goog.require('goog.crypt'); -goog.require('goog.crypt.Sha256'); - - -/** - * Cordova environment utility and helper functions. - * @param {string} authDomain The application authDomain. - * @param {string} apiKey The API key. - * @param {string} appName The App name. - * @param {?string=} clientVersion The optional client version string. - * @param {number=} initialTimeout Initial Auth event timeout. - * @param {number=} redirectTimeout Redirect result timeout. - * @param {?string=} endpointId The endpoint ID (staging, test Gaia, etc). - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration - * @constructor - * @implements {fireauth.OAuthSignInHandler} - */ -fireauth.CordovaHandler = function(authDomain, apiKey, appName, - clientVersion, initialTimeout, redirectTimeout, endpointId, emulatorConfig) { - /** @private {string} The application authDomain. */ - this.authDomain_ = authDomain; - /** @private {string} The application API key. */ - this.apiKey_ = apiKey; - /** @private {string} The application name. */ - this.appName_ = appName; - /** @private {?string} The client version */ - this.clientVersion_ = clientVersion || null; - /** @private {?string} The Auth endpoint ID. */ - this.endpointId_ = endpointId || null; - /** - * @private @const {?fireauth.constants.EmulatorSettings|undefined} - * The emulator configuration - */ - this.emulatorConfig_ = emulatorConfig; - /** @private {string} The storage key. */ - this.storageKey_ = fireauth.util.createStorageKey(apiKey, appName); - /** - * @private {!fireauth.storage.OAuthHandlerManager} The OAuth handler - * storage manager reference, used to save a partial Auth event when - * redirect operation is triggered. - */ - this.savePartialEventManager_ = new fireauth.storage.OAuthHandlerManager(); - /** - * @private {!fireauth.storage.AuthEventManager} The Auth event storage - * manager reference. This is used to get back the saved partial Auth - * event and then delete on successful handling. - */ - this.getAndDeletePartialEventManager_ = - new fireauth.storage.AuthEventManager(this.storageKey_); - /** - * @private {?goog.Promise} A promise that resolves with - * the OAuth redirect URL response. - */ - this.initialAuthEvent_ = null; - /** - * @private {!Array} The Auth event - * listeners. - */ - this.authEventListeners_ = []; - /** @private {number} The initial Auth event timeout. */ - this.initialTimeout_ = initialTimeout || - fireauth.CordovaHandler.INITIAL_TIMEOUT_MS_; - /** @private {number} The return to app after redirect timeout. */ - this.redirectTimeout_ = redirectTimeout || - fireauth.CordovaHandler.REDIRECT_TIMEOUT_MS_; - /** - * @private {?goog.Promise} The last pending redirect promise. This is null if - * already completed. - */ - this.pendingRedirect_ = null; - /** - * @private {?Object} The inAppBrowser reference window if available. This is - * relevant to iOS 7 and 8 embedded webviews. - */ - this.inAppBrowserRef_ = null; -}; - - -/** - * The total number of chars used to generate the session ID string. - * @const {number} - * @private - */ -fireauth.CordovaHandler.SESSION_ID_TOTAL_CHARS_ = 20; - - -/** - * The default initial Auth event timeout in ms. - * @const {number} - * @private - */ -fireauth.CordovaHandler.INITIAL_TIMEOUT_MS_ = 500; - - -/** - * The default timeout in milliseconds for a pending redirect operation after - * returning to the app. - * @const {number} - * @private - */ -fireauth.CordovaHandler.REDIRECT_TIMEOUT_MS_ = 2000; - - -/** - * Constructs a Cordova configuration error message. - * @param {?string=} opt_message The optional error message to be used. This - * will override the existing default one. - * @return {!fireauth.AuthError} The Cordova invalid configuration error with - * the custom message provided. If no message is provided, the default - * message is used. - * @private - */ -fireauth.CordovaHandler.getError_ = function(opt_message) { - return new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION, - opt_message); -}; - - -/** - * Initializes the Cordova environment and waits for it to be ready. - * @return {!goog.Promise} A promise that resolves if the current environment is - * a Cordova environment. - * @override - */ -fireauth.CordovaHandler.prototype.initializeAndWait = function() { - if (this.isReady_) { - return this.isReady_; - } - this.isReady_ = fireauth.util.checkIfCordova().then(function() { - // Check all dependencies installed. - // Note that cordova-universal-links-plugin has been abandoned. - // A fork with latest fixes is available at: - // https://www.npmjs.com/package/cordova-universal-links-plugin-fix - var subscribe = fireauth.util.getObjectRef( - 'universalLinks.subscribe', goog.global); - if (typeof subscribe !== 'function') { - throw fireauth.CordovaHandler.getError_( - 'cordova-universal-links-plugin-fix is not installed'); - } - // https://www.npmjs.com/package/cordova-plugin-buildinfo - var appIdentifier = - fireauth.util.getObjectRef('BuildInfo.packageName', goog.global); - if (typeof appIdentifier === 'undefined') { - throw fireauth.CordovaHandler.getError_( - 'cordova-plugin-buildinfo is not installed'); - } - // https://github.com/google/cordova-plugin-browsertab - var openUrl = fireauth.util.getObjectRef( - 'cordova.plugins.browsertab.openUrl', goog.global); - if (typeof openUrl !== 'function') { - throw fireauth.CordovaHandler.getError_( - 'cordova-plugin-browsertab is not installed'); - } - // https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/ - var openInAppBrowser = fireauth.util.getObjectRef( - 'cordova.InAppBrowser.open', goog.global); - if (typeof openInAppBrowser !== 'function') { - throw fireauth.CordovaHandler.getError_( - 'cordova-plugin-inappbrowser is not installed'); - } - }, function(error) { - // If not supported. - throw new fireauth.AuthError(fireauth.authenum.Error.CORDOVA_NOT_READY); - }); - return this.isReady_; -}; - - -/** - * Generates a session ID. Used to prevent session fixation attacks. - * @param {number} numOfChars The number of characters to generate. - * @return {string} The generated session ID. - * @private - */ -fireauth.CordovaHandler.prototype.generateSessionId_ = function(numOfChars) { - var chars = []; - var allowedChars = - '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - while (numOfChars > 0) { - var index = Math.floor(Math.random() * allowedChars.length); - chars.push(allowedChars.charAt(index)); - numOfChars--; - } - return chars.join(''); -}; - - -/** - * Computes the sha256 hash of a session ID. - * @param {string} str The string to hash. - * @return {string} The hashed string. - * @private - */ -fireauth.CordovaHandler.prototype.computeSecureHash_ = function(str) { - // sha256 the sessionId. This will be passed to the OAuth backend. - // When exchanging the Auth code with a firebase ID token, the raw session ID - // needs to be provided. - var sha256 = new goog.crypt.Sha256(); - sha256.update(str); - return goog.crypt.byteArrayToHex(sha256.digest()); -}; - - -/** - * Waits for popup window to close and time out if the result is unhandled. - * This is not supported in Cordova. - * @param {!Window} popupWin The popup window. - * @param {!function(!fireauth.AuthError)} onError The on error callback. - * @return {!goog.Promise} - * @override - */ -fireauth.CordovaHandler.prototype.startPopupTimeout = - function(popupWin, onError, timeoutDuration) { - // Not supported operation, check processPopup for details. - onError(new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - return goog.Promise.resolve(); -}; - - -/** - * Processes the popup request. This is not supported in Cordova. - * @param {?Window} popupWin The popup window reference. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {function()} onInitialize The function to call on initialization. - * @param {function(*)} onError The function to call on error. - * @param {string=} opt_eventId The optional event ID. - * @param {boolean=} opt_alreadyRedirected Whether popup is already redirected - * to final destination. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} The popup window promise. - * @override - */ -fireauth.CordovaHandler.prototype.processPopup = function( - popupWin, - mode, - provider, - onInitialize, - onError, - opt_eventId, - opt_alreadyRedirected, - opt_tenantId) { - // Popups not supported in Cordova as the activity could be destroyed in - // some cases. Redirect works better as getRedirectResult can be used as a - // fallback to get the result when the activity is detroyed. - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); -}; - - -/** - * @return {boolean} Whether the handler will unload the current page on - * redirect operations. - * @override - */ -fireauth.CordovaHandler.prototype.unloadsOnRedirect = function() { - // Does not necessarily unload the page on redirect. - return false; -}; - - -/** - * @return {boolean} Whether the handler should be initialized early. - * @override - */ -fireauth.CordovaHandler.prototype.shouldBeInitializedEarly = function() { - // Initialize early to detect incoming link. This is not an expensive - // operation, unlike embedding an iframe. - return true; -}; - - -/** - * @return {boolean} Whether the sign-in handler in the current environment - * has volatile session storage. - * @override - */ -fireauth.CordovaHandler.prototype.hasVolatileStorage = function() { - // An activity can be destroyed and thereby sessionStorage wiped out. - return true; -}; - - -/** - * Processes the OAuth redirect request. Will resolve when the OAuth response - * is detected in the incoming link and the corresponding Auth event is - * triggered. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {?string=} opt_eventId The optional event ID. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} - * @override - */ -fireauth.CordovaHandler.prototype.processRedirect = function( - mode, - provider, - opt_eventId, - opt_tenantId) { - // If there is already a pending redirect, throw an error. - if (this.pendingRedirect_) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_OPERATION_PENDING)); - } - var self = this; - var doc = goog.global.document; - // On close timer promise. - var onClose = null; - // Auth event detection callback; - var authEventCallback = null; - // On resume (return from the redirect operation). - var onResume = null; - // On visibility change used to detect return to app in certain versions, - // currently iOS. - var onVisibilityChange = null; - // When the processRedirect promise completes, clean up any remaining - // temporary listeners and timers. - var cleanup = function() { - // Remove current resume listener. - if (onResume) { - doc.removeEventListener('resume', onResume, false); - } - // Remove visibility change listener. - if (onVisibilityChange) { - doc.removeEventListener('visibilitychange', onVisibilityChange, false); - } - // Cancel onClose promise if not already cancelled. - if (onClose) { - onClose.cancel(); - } - // Remove Auth event callback. - if (authEventCallback) { - self.removeAuthEventListener(authEventCallback); - } - // Clear any pending redirect now that it is completed. - self.pendingRedirect_ = null; - }; - // Save the pending redirect promise and clear it on completion. - this.pendingRedirect_ = goog.Promise.resolve().then(function() { - // Validate provider. - // Fail fast in this case. - fireauth.AuthProvider.checkIfOAuthSupported(provider); - return self.getInitialAuthEvent_(); - }).then(function() { - return self.processRedirectInternal_( - mode, provider, opt_eventId, opt_tenantId); - }).then(function() { - // Wait for result (universal link) before resolving this operation. - // This ensures that if the activity is not destroyed, we can still - // return the result of this operation. - return new goog.Promise(function(resolve, reject) { - /** - * @param {?fireauth.AuthEvent} event The Auth event detected. - * @return {boolean} - */ - authEventCallback = function(event) { - // Auth event detected, resolve promise. - // Close SFSVC if still open. - var closeBrowsertab = fireauth.util.getObjectRef( - 'cordova.plugins.browsertab.close', goog.global); - resolve(); - // Close the SFSVC if it is still open (iOS 9+). - if (typeof closeBrowsertab === 'function') { - closeBrowsertab(); - } - // Close inappbrowser emebedded webview in iOS7 and 8 case if still - // open. - if (self.inAppBrowserRef_ && - typeof self.inAppBrowserRef_['close'] === 'function') { - self.inAppBrowserRef_['close'](); - // Reset reference. - self.inAppBrowserRef_ = null; - } - return false; - }; - // Wait and listen for the operation to complete (Auth event would - // trigger). - self.addAuthEventListener(authEventCallback); - // On resume (return from the redirect operation). - onResume = function() { - // Already resumed. Do not run again. - if (onClose) { - return; - } - // Wait for some time before throwing the error that the flow was - // cancelled by the user. - onClose = goog.Timer.promise(self.redirectTimeout_).then(function() { - // Throw the redirect cancelled by user error. - reject(new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_CANCELLED_BY_USER)); - }); - }; - onVisibilityChange = function() { - // If app is visible, run onResume. Otherwise, ignore. - if (fireauth.util.isAppVisible()) { - onResume(); - } - }; - // Listen to resume event (will trigger when the user returns to the app). - doc.addEventListener('resume', onResume, false); - // Listen to visibility change. This is used for iOS Cordova Safari 7+. - // Does not work in Android stock browser versions older than 4.4. - // We rely on resume event in Android as it works reliably in all - // versions. - if (!fireauth.util.isAndroid()) { - doc.addEventListener('visibilitychange', onVisibilityChange, false); - } - }).thenCatch(function(error) { - // Remove any pending partial event. - return self.getPartialStoredEvent_().then(function() { - throw error; - }); - }); - }).thenAlways(cleanup); - // Return the pending redirect promise. - return this.pendingRedirect_; -}; - -/** - * Processes the OAuth redirect request. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {?string=} opt_eventId The optional event ID. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} - * @private - */ -fireauth.CordovaHandler.prototype.processRedirectInternal_ = function( - mode, - provider, - opt_eventId, - opt_tenantId) { - var self = this; - // https://github.com/google/cordova-plugin-browsertab - // Opens chrome custom tab in Android if chrome is installed, - // SFSafariViewController in iOS if supported. - // If the above are not supported, opens the system browser. - // Opening a system browser could result in an app being rejected in the App - // Store. The only solution here is to use an insecure embedded UIWebView. - // This applies to older iOS versions 8 and under. - // Generate a random session ID. - var sessionId = this.generateSessionId_( - fireauth.CordovaHandler.SESSION_ID_TOTAL_CHARS_); - // Create the partial Auth event. - var event = new fireauth.AuthEvent( - mode, - opt_eventId, - null, - sessionId, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT), - null, - opt_tenantId); - // Use buildinfo package to get app metadata. - // https://www.npmjs.com/package/cordova-plugin-buildinfo - // Get app identifier. - var appIdentifier = - fireauth.util.getObjectRef('BuildInfo.packageName', goog.global); - // initializeAndWait will ensure this does not happen. - if (typeof appIdentifier !== 'string') { - throw new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION); - } - // Get app display name. - var appDisplayName = - fireauth.util.getObjectRef('BuildInfo.displayName', goog.global); - // Construct additional params to pass to OAuth handler. - var additionalParams = {}; - // Append app identifier. - if (fireauth.util.isIOS()) { - // iOS app. - additionalParams['ibi'] = appIdentifier; - } else if (fireauth.util.isAndroid()) { - // Android app. - additionalParams['apn'] = appIdentifier; - } else { - // This should not happen as Cordova handler should not even be used in this - // case. - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED)); - } - // Pass app display name. - if (appDisplayName) { - additionalParams['appDisplayName'] = appDisplayName; - } - // Hash the session ID and pass it to additional params. - var hashedSessionId = this.computeSecureHash_(sessionId); - // Append session ID. - additionalParams['sessionId'] = hashedSessionId; - // Construct OAuth handler URL. - var oauthHelperWidgetUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - this.authDomain_, - this.apiKey_, - this.appName_, - mode, - provider, - null, - opt_eventId, - this.clientVersion_, - additionalParams, - this.endpointId_, - opt_tenantId, - this.emulatorConfig_); - // Make sure handler initialized and ready. - // This should also ensure all plugins are installed. - return this.initializeAndWait().then(function() { - // Save partial Auth event. - return self.savePartialEventManager_.setAuthEvent(self.storageKey_, event); - }).then(function() { - // initializeAndWait will ensure this plugin is installed. - var isAvailable = /** @type {!function(!function(*))} */ ( - fireauth.util.getObjectRef( - 'cordova.plugins.browsertab.isAvailable', goog.global)); - if (typeof isAvailable !== 'function') { - throw new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION); - } - var openUrl = null; - // Check if browsertab is supported. - isAvailable(function(result) { - if (result) { - // browsertab supported. - openUrl = /** @type {!function(string, ...*)} */ ( - fireauth.util.getObjectRef( - 'cordova.plugins.browsertab.openUrl', goog.global)); - if (typeof openUrl !== 'function') { - throw new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION); - } - // Open OAuth handler. - openUrl(oauthHelperWidgetUrl); - } else { - // browsertab not supported, switch to inappbrowser. - openUrl = /** @type {!function(string, string, string=)} */ ( - fireauth.util.getObjectRef( - 'cordova.InAppBrowser.open', goog.global)); - if (typeof openUrl !== 'function') { - throw new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION); - } - // Open in embedded webview for iOS 7 and 8 as Apple rejects apps that - // switch context. - // _blank opens an embedded webview. - // _system opens the system browser. - // _system (opens a system browser) is used as a fallback when - // browsertab plugin is unable to open a chromecustomtab or SFSVC. - // This has to exclude all iOS older versions where switching to a - // browser is frowned upon by Apple and embedding a UIWebView is the - // only option but is insecure and deprecated by Google for OAuth - // sign-in. This will be applicable in old versions of Android. - self.inAppBrowserRef_ = openUrl( - oauthHelperWidgetUrl, - fireauth.util.isIOS7Or8() ? '_blank' : '_system', - 'location=yes'); - } - }); - }); -}; - - -/** - * Dispatches the detected Auth event to all subscribed listeners. - * @param {!fireauth.AuthEvent} event A detected Auth event. - * @private - */ -fireauth.CordovaHandler.prototype.dispatchEvent_ = function(event) { - for (var i = 0; i < this.authEventListeners_.length; i++) { - try { - this.authEventListeners_[i](event); - } catch (e) { - // If any handler fails, ignore and run next handler. - } - } -}; - - -/** - * Resolves the first redirect Auth event and caches it. - * @return {!goog.Promise} A promise that resolves with the - * initial Auth event response from a redirect operation. Initializes the - * internal Auth event listener which will dispatch Auth events to all - * subscribed listeners. - * @private - */ -fireauth.CordovaHandler.prototype.getInitialAuthEvent_ = function() { - var self = this; - if (!this.initialAuthEvent_) { - // Cache this result so on next call, it is not triggered again. - this.initialAuthEvent_ = this.initializeAndWait().then(function() { - return new goog.Promise(function(resolve, reject) { - /** - * @param {?fireauth.AuthEvent} event The Auth event detected. - * @return {boolean} - */ - var authEventCallback = function(event) { - resolve(event); - // Remove on completion. - self.removeAuthEventListener(authEventCallback); - return false; - }; - // Listen to Auth events. If resolved, resolve promise. - self.addAuthEventListener(authEventCallback); - // This should succeed as initializeAndWait should guarantee plugins are - // ready. - self.setAuthEventListener_(); - }); - }); - } - return this.initialAuthEvent_; -}; - - -/** - * Gets and deletes the current stored partial event from storage. - * @return {!goog.Promise} A promise that resolves with the - * stored Auth event. - * @private - */ -fireauth.CordovaHandler.prototype.getPartialStoredEvent_ = function() { - var event = null; - var self = this; - // Get any saved partial Auth event. - return this.getAndDeletePartialEventManager_.getAuthEvent() - .then(function(authEvent) { - // Save partial event locally. - event = authEvent; - // Delete partial event. - return self.getAndDeletePartialEventManager_.removeAuthEvent(); - }).then(function() { - // Return the locally saved partial event. - return event; - }); -}; - - -/** - * Extracts the Auth event pertaining to the incoming URL. - * @param {!fireauth.AuthEvent} partialEvent The partial Auth event. - * @param {string} url The incoming universal link. - * @return {?fireauth.AuthEvent} The resolved Auth event corresponding to the - * callback URL. This is null if no event is found. - * @private - */ -fireauth.CordovaHandler.prototype.extractAuthEventFromUrl_ = - function(partialEvent, url) { - // Default no redirect event result. - var authEvent = null; - // Parse the deep link within the dynamic link URL. - var callbackUrl = fireauth.DynamicLink.parseDeepLink(url); - // Confirm it is actually a callback URL. - // Currently the universal link will be of this format: - // https:///__/auth/callback - // This is a fake URL but is not intended to take the user anywhere - // and just redirect to the app. - if (callbackUrl.indexOf('/__/auth/callback') != -1) { - // Check if there is an error in the URL. - // This mechanism is also used to pass errors back to the app: - // https:///__/auth/callback?firebaseError= - var uri = goog.Uri.parse(callbackUrl); - // Get the error object corresponding to the stringified error if found. - var errorObject = fireauth.util.parseJSON( - uri.getParameterValue('firebaseError') || null); - var error = typeof errorObject === 'object' ? - fireauth.AuthError.fromPlainObject( - /** @type {?Object} */ (errorObject)) : - null; - if (error) { - // Construct the full failed Auth event. - authEvent = new fireauth.AuthEvent( - partialEvent.getType(), - partialEvent.getEventId(), - null, - null, - error, - null, - partialEvent.getTenantId()); - } else { - // Construct the full successful Auth event. - authEvent = new fireauth.AuthEvent( - partialEvent.getType(), - partialEvent.getEventId(), - callbackUrl, - partialEvent.getSessionId(), - null, - null, - partialEvent.getTenantId()); - } - } - return authEvent; -}; - - -/** - * Sets the internal Auth event listener. This listens to incoming universal - * links and on detection, repackages them into an Auth event and then - * dispatches the events in all event listeners. - * @private - */ -fireauth.CordovaHandler.prototype.setAuthEventListener_ = function() { - // https://github.com/nordnet/cordova-universal-links-plugin-fix - var self = this; - // Default no redirect event result. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var initialResolve = false; - // On initialization, if no incoming universal link detected, trigger - // no Auth event (no redirect operation previously called) after waiting - // for a short period of time. - var noEventTimer = goog.Timer.promise(this.initialTimeout_).then(function() { - // Delete any pending unhandled event. - return self.getPartialStoredEvent_().then(function(event) { - // On timeout trigger noEvent if not already resolved in link - // subscriber. - if (!initialResolve) { - self.dispatchEvent_(noEvent); - } - }); - }); - // No event name needed, subscribe to all incoming universal links. - var universalLinkCb = function(eventData) { - initialResolve = true; - // Cancel no event timer. - if (noEventTimer) { - noEventTimer.cancel(); - } - // Incoming link detected. - // Check for any stored partial event. - self.getPartialStoredEvent_().then(function(event) { - // Initialize to an unknown event. - var authEvent = noEvent; - // Confirm OAuth response included. - if (event && eventData && eventData['url']) { - // Construct complete event. Default to unknown event if none found. - authEvent = self.extractAuthEventFromUrl_(event, eventData['url']) || - noEvent; - } - // Dispatch Auth event. - self.dispatchEvent_(authEvent); - }); - }; - // iOS 7 or 8 custom URL schemes. - // This is also the current default behavior for iOS 9+. - // For this to work, cordova-plugin-customurlscheme needs to be installed. - // https://github.com/EddyVerbruggen/Custom-URL-scheme - // Do not overwrite the existing developer's URL handler. - var existingHandlerOpenURL = goog.global['handleOpenURL']; - goog.global['handleOpenURL'] = function(url) { - var appIdentifier = - fireauth.util.getObjectRef('BuildInfo.packageName', goog.global); - // Apply case insensitive match. While bundle IDs are case sensitive, - // when creating a new app, Apple verifies the Bundle ID using - // case-insensitive search. So it is not possible that an app in the app - // store try to impersonate another one by lower/upper casing characters. - if (url.toLowerCase().indexOf(appIdentifier.toLowerCase() + '://') == 0) { - universalLinkCb({ - 'url': url - }); - } - // Call the developer's handler if it is present. - if (typeof existingHandlerOpenURL === 'function') { - try { - existingHandlerOpenURL(url); - } catch(e) { - // This doesn't swallow the error but also does not interrupt the flow. - console.error(e); - } - } - }; - fireauth.UniversalLinkSubscriber.getInstance().subscribe(universalLinkCb); -}; - - -/** - * @param {!function(?fireauth.AuthEvent):boolean} listener The Auth event - * listener to add. - * @override - */ -fireauth.CordovaHandler.prototype.addAuthEventListener = function(listener) { - // TODO: consider creating an abstract base class that OAuth handlers - // extend with add, remove Auth event listeners and dispatcher methods. - this.authEventListeners_.push(listener); - // Set internal listener to Auth events. This will be ignored on subsequent - // calls. - this.getInitialAuthEvent_().thenCatch(function(error) { - // Suppress this error as it should be caught through other actionable - // public methods. - // This would typically happen on invalid Cordova setup, when the OAuth - // plugins are not installed. This should still trigger the Auth event - // as developers are not forced to use OAuth sign-in in their Cordova app. - // This is needed for onAuthStateChanged listener to trigger initially. - if (error.code === 'auth/invalid-cordova-configuration') { - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - listener(noEvent); - } - }); -}; - - -/** - * @param {!function(?fireauth.AuthEvent):boolean} listener The Auth event - * listener to remove. - * @override - */ -fireauth.CordovaHandler.prototype.removeAuthEventListener = function(listener) { - goog.array.removeAllIf(this.authEventListeners_, function(ele) { - return ele == listener; - }); -}; - diff --git a/packages-exp/auth-exp/src/core/action_code_url.test.ts b/packages/auth/src/core/action_code_url.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/action_code_url.test.ts rename to packages/auth/src/core/action_code_url.test.ts diff --git a/packages-exp/auth-exp/src/core/action_code_url.ts b/packages/auth/src/core/action_code_url.ts similarity index 100% rename from packages-exp/auth-exp/src/core/action_code_url.ts rename to packages/auth/src/core/action_code_url.ts diff --git a/packages-exp/auth-exp/src/core/auth/auth_event_manager.test.ts b/packages/auth/src/core/auth/auth_event_manager.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/auth/auth_event_manager.test.ts rename to packages/auth/src/core/auth/auth_event_manager.test.ts diff --git a/packages-exp/auth-exp/src/core/auth/auth_event_manager.ts b/packages/auth/src/core/auth/auth_event_manager.ts similarity index 100% rename from packages-exp/auth-exp/src/core/auth/auth_event_manager.ts rename to packages/auth/src/core/auth/auth_event_manager.ts diff --git a/packages-exp/auth-exp/src/core/auth/auth_impl.test.ts b/packages/auth/src/core/auth/auth_impl.test.ts similarity index 99% rename from packages-exp/auth-exp/src/core/auth/auth_impl.test.ts rename to packages/auth/src/core/auth/auth_impl.test.ts index 2799bf62719..d8b8e088381 100644 --- a/packages-exp/auth-exp/src/core/auth/auth_impl.test.ts +++ b/packages/auth/src/core/auth/auth_impl.test.ts @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { FirebaseError } from '@firebase/util'; import { testAuth, testUser } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts similarity index 99% rename from packages-exp/auth-exp/src/core/auth/auth_impl.ts rename to packages/auth/src/core/auth/auth_impl.ts index b6eeedcc456..af3258a8433 100644 --- a/packages-exp/auth-exp/src/core/auth/auth_impl.ts +++ b/packages/auth/src/core/auth/auth_impl.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _FirebaseService, FirebaseApp } from '@firebase/app-exp'; +import { _FirebaseService, FirebaseApp } from '@firebase/app'; import { Auth, AuthErrorMap, diff --git a/packages-exp/auth-exp/src/core/auth/emulator.test.ts b/packages/auth/src/core/auth/emulator.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/auth/emulator.test.ts rename to packages/auth/src/core/auth/emulator.test.ts diff --git a/packages-exp/auth-exp/src/core/auth/emulator.ts b/packages/auth/src/core/auth/emulator.ts similarity index 100% rename from packages-exp/auth-exp/src/core/auth/emulator.ts rename to packages/auth/src/core/auth/emulator.ts diff --git a/packages-exp/auth-exp/src/core/auth/firebase_internal.test.ts b/packages/auth/src/core/auth/firebase_internal.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/auth/firebase_internal.test.ts rename to packages/auth/src/core/auth/firebase_internal.test.ts diff --git a/packages-exp/auth-exp/src/core/auth/firebase_internal.ts b/packages/auth/src/core/auth/firebase_internal.ts similarity index 100% rename from packages-exp/auth-exp/src/core/auth/firebase_internal.ts rename to packages/auth/src/core/auth/firebase_internal.ts diff --git a/packages-exp/auth-exp/src/core/auth/initialize.test.ts b/packages/auth/src/core/auth/initialize.test.ts similarity index 99% rename from packages-exp/auth-exp/src/core/auth/initialize.test.ts rename to packages/auth/src/core/auth/initialize.test.ts index 7226d5b5374..e1c2a6255b9 100644 --- a/packages-exp/auth-exp/src/core/auth/initialize.test.ts +++ b/packages/auth/src/core/auth/initialize.test.ts @@ -20,7 +20,7 @@ import { initializeApp, FirebaseApp, _FirebaseService -} from '@firebase/app-exp'; +} from '@firebase/app'; import { Auth, AuthProvider, diff --git a/packages-exp/auth-exp/src/core/auth/initialize.ts b/packages/auth/src/core/auth/initialize.ts similarity index 96% rename from packages-exp/auth-exp/src/core/auth/initialize.ts rename to packages/auth/src/core/auth/initialize.ts index 7ac8323d9b3..c6218953508 100644 --- a/packages-exp/auth-exp/src/core/auth/initialize.ts +++ b/packages/auth/src/core/auth/initialize.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _getProvider, FirebaseApp } from '@firebase/app-exp'; +import { _getProvider, FirebaseApp } from '@firebase/app'; import { deepEqual } from '@firebase/util'; import { Auth, Dependencies } from '../../model/public_types'; @@ -51,7 +51,7 @@ import { AuthImpl } from './auth_impl'; * @public */ export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth { - const provider = _getProvider(app, 'auth-exp'); + const provider = _getProvider(app, 'auth'); if (provider.isInitialized()) { const auth = provider.getImmediate() as AuthImpl; diff --git a/packages-exp/auth-exp/src/core/auth/register.ts b/packages/auth/src/core/auth/register.ts similarity index 97% rename from packages-exp/auth-exp/src/core/auth/register.ts rename to packages/auth/src/core/auth/register.ts index 7d7664d2181..dcd3f9f420c 100644 --- a/packages-exp/auth-exp/src/core/auth/register.ts +++ b/packages/auth/src/core/auth/register.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _registerComponent, registerVersion } from '@firebase/app-exp'; +import { _registerComponent, registerVersion } from '@firebase/app'; import { Component, ComponentType, @@ -33,7 +33,7 @@ import { Dependencies } from '../../model/public_types'; import { _initializeAuthInstance } from './initialize'; export const enum _ComponentName { - AUTH = 'auth-exp', + AUTH = 'auth', AUTH_INTERNAL = 'auth-internal' } @@ -60,7 +60,7 @@ export function registerAuth(clientPlatform: ClientPlatform): void { new Component( _ComponentName.AUTH, (container, { options: deps }: { options?: Dependencies }) => { - const app = container.getProvider('app-exp').getImmediate()!; + const app = container.getProvider('app').getImmediate()!; const { apiKey, authDomain } = app.options; return (app => { _assert( diff --git a/packages-exp/auth-exp/src/core/credentials/auth_credential.ts b/packages/auth/src/core/credentials/auth_credential.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/auth_credential.ts rename to packages/auth/src/core/credentials/auth_credential.ts diff --git a/packages-exp/auth-exp/src/core/credentials/email.test.ts b/packages/auth/src/core/credentials/email.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/email.test.ts rename to packages/auth/src/core/credentials/email.test.ts diff --git a/packages-exp/auth-exp/src/core/credentials/email.ts b/packages/auth/src/core/credentials/email.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/email.ts rename to packages/auth/src/core/credentials/email.ts diff --git a/packages-exp/auth-exp/src/core/credentials/index.ts b/packages/auth/src/core/credentials/index.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/index.ts rename to packages/auth/src/core/credentials/index.ts diff --git a/packages-exp/auth-exp/src/core/credentials/oauth.test.ts b/packages/auth/src/core/credentials/oauth.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/oauth.test.ts rename to packages/auth/src/core/credentials/oauth.test.ts diff --git a/packages-exp/auth-exp/src/core/credentials/oauth.ts b/packages/auth/src/core/credentials/oauth.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/oauth.ts rename to packages/auth/src/core/credentials/oauth.ts diff --git a/packages-exp/auth-exp/src/core/credentials/phone.test.ts b/packages/auth/src/core/credentials/phone.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/phone.test.ts rename to packages/auth/src/core/credentials/phone.test.ts diff --git a/packages-exp/auth-exp/src/core/credentials/phone.ts b/packages/auth/src/core/credentials/phone.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/phone.ts rename to packages/auth/src/core/credentials/phone.ts diff --git a/packages-exp/auth-exp/src/core/credentials/saml.test.ts b/packages/auth/src/core/credentials/saml.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/saml.test.ts rename to packages/auth/src/core/credentials/saml.test.ts diff --git a/packages-exp/auth-exp/src/core/credentials/saml.ts b/packages/auth/src/core/credentials/saml.ts similarity index 100% rename from packages-exp/auth-exp/src/core/credentials/saml.ts rename to packages/auth/src/core/credentials/saml.ts diff --git a/packages-exp/auth-exp/src/core/errors.test.ts b/packages/auth/src/core/errors.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/errors.test.ts rename to packages/auth/src/core/errors.test.ts diff --git a/packages-exp/auth-exp/src/core/errors.ts b/packages/auth/src/core/errors.ts similarity index 100% rename from packages-exp/auth-exp/src/core/errors.ts rename to packages/auth/src/core/errors.ts diff --git a/packages-exp/auth-exp/src/core/index.ts b/packages/auth/src/core/index.ts similarity index 100% rename from packages-exp/auth-exp/src/core/index.ts rename to packages/auth/src/core/index.ts diff --git a/packages-exp/auth-exp/src/core/persistence/in_memory.test.ts b/packages/auth/src/core/persistence/in_memory.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/persistence/in_memory.test.ts rename to packages/auth/src/core/persistence/in_memory.test.ts diff --git a/packages-exp/auth-exp/src/core/persistence/in_memory.ts b/packages/auth/src/core/persistence/in_memory.ts similarity index 100% rename from packages-exp/auth-exp/src/core/persistence/in_memory.ts rename to packages/auth/src/core/persistence/in_memory.ts diff --git a/packages-exp/auth-exp/src/core/persistence/index.ts b/packages/auth/src/core/persistence/index.ts similarity index 100% rename from packages-exp/auth-exp/src/core/persistence/index.ts rename to packages/auth/src/core/persistence/index.ts diff --git a/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.test.ts b/packages/auth/src/core/persistence/persistence_user_manager.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/persistence/persistence_user_manager.test.ts rename to packages/auth/src/core/persistence/persistence_user_manager.test.ts diff --git a/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.ts b/packages/auth/src/core/persistence/persistence_user_manager.ts similarity index 100% rename from packages-exp/auth-exp/src/core/persistence/persistence_user_manager.ts rename to packages/auth/src/core/persistence/persistence_user_manager.ts diff --git a/packages-exp/auth-exp/src/core/providers/email.test.ts b/packages/auth/src/core/providers/email.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/email.test.ts rename to packages/auth/src/core/providers/email.test.ts diff --git a/packages-exp/auth-exp/src/core/providers/email.ts b/packages/auth/src/core/providers/email.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/email.ts rename to packages/auth/src/core/providers/email.ts diff --git a/packages-exp/auth-exp/src/core/providers/facebook.test.ts b/packages/auth/src/core/providers/facebook.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/facebook.test.ts rename to packages/auth/src/core/providers/facebook.test.ts diff --git a/packages-exp/auth-exp/src/core/providers/facebook.ts b/packages/auth/src/core/providers/facebook.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/facebook.ts rename to packages/auth/src/core/providers/facebook.ts diff --git a/packages-exp/auth-exp/src/core/providers/federated.test.ts b/packages/auth/src/core/providers/federated.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/federated.test.ts rename to packages/auth/src/core/providers/federated.test.ts diff --git a/packages-exp/auth-exp/src/core/providers/federated.ts b/packages/auth/src/core/providers/federated.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/federated.ts rename to packages/auth/src/core/providers/federated.ts diff --git a/packages-exp/auth-exp/src/core/providers/github.test.ts b/packages/auth/src/core/providers/github.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/github.test.ts rename to packages/auth/src/core/providers/github.test.ts diff --git a/packages-exp/auth-exp/src/core/providers/github.ts b/packages/auth/src/core/providers/github.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/github.ts rename to packages/auth/src/core/providers/github.ts diff --git a/packages-exp/auth-exp/src/core/providers/google.test.ts b/packages/auth/src/core/providers/google.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/google.test.ts rename to packages/auth/src/core/providers/google.test.ts diff --git a/packages-exp/auth-exp/src/core/providers/google.ts b/packages/auth/src/core/providers/google.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/google.ts rename to packages/auth/src/core/providers/google.ts diff --git a/packages-exp/auth-exp/src/core/providers/oauth.test.ts b/packages/auth/src/core/providers/oauth.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/oauth.test.ts rename to packages/auth/src/core/providers/oauth.test.ts diff --git a/packages-exp/auth-exp/src/core/providers/oauth.ts b/packages/auth/src/core/providers/oauth.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/oauth.ts rename to packages/auth/src/core/providers/oauth.ts diff --git a/packages-exp/auth-exp/src/core/providers/saml.test.ts b/packages/auth/src/core/providers/saml.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/saml.test.ts rename to packages/auth/src/core/providers/saml.test.ts diff --git a/packages-exp/auth-exp/src/core/providers/saml.ts b/packages/auth/src/core/providers/saml.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/saml.ts rename to packages/auth/src/core/providers/saml.ts diff --git a/packages-exp/auth-exp/src/core/providers/twitter.test.ts b/packages/auth/src/core/providers/twitter.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/twitter.test.ts rename to packages/auth/src/core/providers/twitter.test.ts diff --git a/packages-exp/auth-exp/src/core/providers/twitter.ts b/packages/auth/src/core/providers/twitter.ts similarity index 100% rename from packages-exp/auth-exp/src/core/providers/twitter.ts rename to packages/auth/src/core/providers/twitter.ts diff --git a/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts b/packages/auth/src/core/strategies/abstract_popup_redirect_operation.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts rename to packages/auth/src/core/strategies/abstract_popup_redirect_operation.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.ts b/packages/auth/src/core/strategies/abstract_popup_redirect_operation.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.ts rename to packages/auth/src/core/strategies/abstract_popup_redirect_operation.ts diff --git a/packages-exp/auth-exp/src/core/strategies/action_code_settings.test.ts b/packages/auth/src/core/strategies/action_code_settings.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/action_code_settings.test.ts rename to packages/auth/src/core/strategies/action_code_settings.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/action_code_settings.ts b/packages/auth/src/core/strategies/action_code_settings.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/action_code_settings.ts rename to packages/auth/src/core/strategies/action_code_settings.ts diff --git a/packages-exp/auth-exp/src/core/strategies/anonymous.test.ts b/packages/auth/src/core/strategies/anonymous.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/anonymous.test.ts rename to packages/auth/src/core/strategies/anonymous.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/anonymous.ts b/packages/auth/src/core/strategies/anonymous.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/anonymous.ts rename to packages/auth/src/core/strategies/anonymous.ts diff --git a/packages-exp/auth-exp/src/core/strategies/credential.test.ts b/packages/auth/src/core/strategies/credential.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/credential.test.ts rename to packages/auth/src/core/strategies/credential.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/credential.ts b/packages/auth/src/core/strategies/credential.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/credential.ts rename to packages/auth/src/core/strategies/credential.ts diff --git a/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts b/packages/auth/src/core/strategies/custom_token.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/custom_token.test.ts rename to packages/auth/src/core/strategies/custom_token.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/custom_token.ts b/packages/auth/src/core/strategies/custom_token.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/custom_token.ts rename to packages/auth/src/core/strategies/custom_token.ts diff --git a/packages-exp/auth-exp/src/core/strategies/email.test.ts b/packages/auth/src/core/strategies/email.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/email.test.ts rename to packages/auth/src/core/strategies/email.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/email.ts b/packages/auth/src/core/strategies/email.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/email.ts rename to packages/auth/src/core/strategies/email.ts diff --git a/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts b/packages/auth/src/core/strategies/email_and_password.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts rename to packages/auth/src/core/strategies/email_and_password.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/email_and_password.ts b/packages/auth/src/core/strategies/email_and_password.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/email_and_password.ts rename to packages/auth/src/core/strategies/email_and_password.ts diff --git a/packages-exp/auth-exp/src/core/strategies/email_link.test.ts b/packages/auth/src/core/strategies/email_link.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/email_link.test.ts rename to packages/auth/src/core/strategies/email_link.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/email_link.ts b/packages/auth/src/core/strategies/email_link.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/email_link.ts rename to packages/auth/src/core/strategies/email_link.ts diff --git a/packages-exp/auth-exp/src/core/strategies/idp.test.ts b/packages/auth/src/core/strategies/idp.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/idp.test.ts rename to packages/auth/src/core/strategies/idp.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/idp.ts b/packages/auth/src/core/strategies/idp.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/idp.ts rename to packages/auth/src/core/strategies/idp.ts diff --git a/packages-exp/auth-exp/src/core/strategies/redirect.test.ts b/packages/auth/src/core/strategies/redirect.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/redirect.test.ts rename to packages/auth/src/core/strategies/redirect.test.ts diff --git a/packages-exp/auth-exp/src/core/strategies/redirect.ts b/packages/auth/src/core/strategies/redirect.ts similarity index 100% rename from packages-exp/auth-exp/src/core/strategies/redirect.ts rename to packages/auth/src/core/strategies/redirect.ts diff --git a/packages-exp/auth-exp/src/core/user/account_info.test.ts b/packages/auth/src/core/user/account_info.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/account_info.test.ts rename to packages/auth/src/core/user/account_info.test.ts diff --git a/packages-exp/auth-exp/src/core/user/account_info.ts b/packages/auth/src/core/user/account_info.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/account_info.ts rename to packages/auth/src/core/user/account_info.ts diff --git a/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts b/packages/auth/src/core/user/additional_user_info.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/additional_user_info.test.ts rename to packages/auth/src/core/user/additional_user_info.test.ts diff --git a/packages-exp/auth-exp/src/core/user/additional_user_info.ts b/packages/auth/src/core/user/additional_user_info.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/additional_user_info.ts rename to packages/auth/src/core/user/additional_user_info.ts diff --git a/packages-exp/auth-exp/src/core/user/id_token_result.test.ts b/packages/auth/src/core/user/id_token_result.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/id_token_result.test.ts rename to packages/auth/src/core/user/id_token_result.test.ts diff --git a/packages-exp/auth-exp/src/core/user/id_token_result.ts b/packages/auth/src/core/user/id_token_result.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/id_token_result.ts rename to packages/auth/src/core/user/id_token_result.ts diff --git a/packages-exp/auth-exp/src/core/user/invalidation.test.ts b/packages/auth/src/core/user/invalidation.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/invalidation.test.ts rename to packages/auth/src/core/user/invalidation.test.ts diff --git a/packages-exp/auth-exp/src/core/user/invalidation.ts b/packages/auth/src/core/user/invalidation.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/invalidation.ts rename to packages/auth/src/core/user/invalidation.ts diff --git a/packages-exp/auth-exp/src/core/user/link_unlink.test.ts b/packages/auth/src/core/user/link_unlink.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/link_unlink.test.ts rename to packages/auth/src/core/user/link_unlink.test.ts diff --git a/packages-exp/auth-exp/src/core/user/link_unlink.ts b/packages/auth/src/core/user/link_unlink.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/link_unlink.ts rename to packages/auth/src/core/user/link_unlink.ts diff --git a/packages-exp/auth-exp/src/core/user/proactive_refresh.test.ts b/packages/auth/src/core/user/proactive_refresh.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/proactive_refresh.test.ts rename to packages/auth/src/core/user/proactive_refresh.test.ts diff --git a/packages-exp/auth-exp/src/core/user/proactive_refresh.ts b/packages/auth/src/core/user/proactive_refresh.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/proactive_refresh.ts rename to packages/auth/src/core/user/proactive_refresh.ts diff --git a/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts b/packages/auth/src/core/user/reauthenticate.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/reauthenticate.test.ts rename to packages/auth/src/core/user/reauthenticate.test.ts diff --git a/packages-exp/auth-exp/src/core/user/reauthenticate.ts b/packages/auth/src/core/user/reauthenticate.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/reauthenticate.ts rename to packages/auth/src/core/user/reauthenticate.ts diff --git a/packages-exp/auth-exp/src/core/user/reload.test.ts b/packages/auth/src/core/user/reload.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/reload.test.ts rename to packages/auth/src/core/user/reload.test.ts diff --git a/packages-exp/auth-exp/src/core/user/reload.ts b/packages/auth/src/core/user/reload.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/reload.ts rename to packages/auth/src/core/user/reload.ts diff --git a/packages-exp/auth-exp/src/core/user/token_manager.test.ts b/packages/auth/src/core/user/token_manager.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/token_manager.test.ts rename to packages/auth/src/core/user/token_manager.test.ts diff --git a/packages-exp/auth-exp/src/core/user/token_manager.ts b/packages/auth/src/core/user/token_manager.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/token_manager.ts rename to packages/auth/src/core/user/token_manager.ts diff --git a/packages-exp/auth-exp/src/core/user/user_credential_impl.test.ts b/packages/auth/src/core/user/user_credential_impl.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/user_credential_impl.test.ts rename to packages/auth/src/core/user/user_credential_impl.test.ts diff --git a/packages-exp/auth-exp/src/core/user/user_credential_impl.ts b/packages/auth/src/core/user/user_credential_impl.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/user_credential_impl.ts rename to packages/auth/src/core/user/user_credential_impl.ts diff --git a/packages-exp/auth-exp/src/core/user/user_impl.test.ts b/packages/auth/src/core/user/user_impl.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/user_impl.test.ts rename to packages/auth/src/core/user/user_impl.test.ts diff --git a/packages-exp/auth-exp/src/core/user/user_impl.ts b/packages/auth/src/core/user/user_impl.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/user_impl.ts rename to packages/auth/src/core/user/user_impl.ts diff --git a/packages-exp/auth-exp/src/core/user/user_metadata.ts b/packages/auth/src/core/user/user_metadata.ts similarity index 100% rename from packages-exp/auth-exp/src/core/user/user_metadata.ts rename to packages/auth/src/core/user/user_metadata.ts diff --git a/packages-exp/auth-exp/src/core/util/assert.test.ts b/packages/auth/src/core/util/assert.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/assert.test.ts rename to packages/auth/src/core/util/assert.test.ts diff --git a/packages-exp/auth-exp/src/core/util/assert.ts b/packages/auth/src/core/util/assert.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/assert.ts rename to packages/auth/src/core/util/assert.ts diff --git a/packages-exp/auth-exp/src/core/util/browser.test.ts b/packages/auth/src/core/util/browser.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/browser.test.ts rename to packages/auth/src/core/util/browser.test.ts diff --git a/packages-exp/auth-exp/src/core/util/browser.ts b/packages/auth/src/core/util/browser.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/browser.ts rename to packages/auth/src/core/util/browser.ts diff --git a/packages-exp/auth-exp/src/core/util/delay.test.ts b/packages/auth/src/core/util/delay.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/delay.test.ts rename to packages/auth/src/core/util/delay.test.ts diff --git a/packages-exp/auth-exp/src/core/util/delay.ts b/packages/auth/src/core/util/delay.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/delay.ts rename to packages/auth/src/core/util/delay.ts diff --git a/packages-exp/auth-exp/src/core/util/emulator.test.ts b/packages/auth/src/core/util/emulator.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/emulator.test.ts rename to packages/auth/src/core/util/emulator.test.ts diff --git a/packages-exp/auth-exp/src/core/util/emulator.ts b/packages/auth/src/core/util/emulator.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/emulator.ts rename to packages/auth/src/core/util/emulator.ts diff --git a/packages-exp/auth-exp/src/core/util/event_id.test.ts b/packages/auth/src/core/util/event_id.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/event_id.test.ts rename to packages/auth/src/core/util/event_id.test.ts diff --git a/packages-exp/auth-exp/src/core/util/event_id.ts b/packages/auth/src/core/util/event_id.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/event_id.ts rename to packages/auth/src/core/util/event_id.ts diff --git a/packages-exp/auth-exp/src/core/util/fetch_provider.ts b/packages/auth/src/core/util/fetch_provider.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/fetch_provider.ts rename to packages/auth/src/core/util/fetch_provider.ts diff --git a/packages-exp/auth-exp/src/core/util/handler.ts b/packages/auth/src/core/util/handler.ts similarity index 98% rename from packages-exp/auth-exp/src/core/util/handler.ts rename to packages/auth/src/core/util/handler.ts index ce8935b0c99..4d21b6cdd7b 100644 --- a/packages-exp/auth-exp/src/core/util/handler.ts +++ b/packages/auth/src/core/util/handler.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; import { AuthProvider } from '../../model/public_types'; import { ApiKey, AppName, AuthInternal } from '../../model/auth'; import { AuthEventType } from '../../model/popup_redirect'; diff --git a/packages-exp/auth-exp/src/core/util/instantiator.test.ts b/packages/auth/src/core/util/instantiator.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/instantiator.test.ts rename to packages/auth/src/core/util/instantiator.test.ts diff --git a/packages-exp/auth-exp/src/core/util/instantiator.ts b/packages/auth/src/core/util/instantiator.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/instantiator.ts rename to packages/auth/src/core/util/instantiator.ts diff --git a/packages-exp/auth-exp/src/core/util/location.ts b/packages/auth/src/core/util/location.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/location.ts rename to packages/auth/src/core/util/location.ts diff --git a/packages-exp/auth-exp/src/core/util/log.ts b/packages/auth/src/core/util/log.ts similarity index 92% rename from packages-exp/auth-exp/src/core/util/log.ts rename to packages/auth/src/core/util/log.ts index c0d6d85ef7a..649c35d4317 100644 --- a/packages-exp/auth-exp/src/core/util/log.ts +++ b/packages/auth/src/core/util/log.ts @@ -16,11 +16,11 @@ */ import { Logger, LogLevel } from '@firebase/logger'; -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; export { LogLevel }; -const logClient = new Logger('@firebase/auth-exp'); +const logClient = new Logger('@firebase/auth'); // Helper methods are needed because variables can't be exported as read/write export function _getLogLevel(): LogLevel { diff --git a/packages-exp/auth-exp/src/core/util/navigator.ts b/packages/auth/src/core/util/navigator.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/navigator.ts rename to packages/auth/src/core/util/navigator.ts diff --git a/packages-exp/auth-exp/src/core/util/providers.ts b/packages/auth/src/core/util/providers.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/providers.ts rename to packages/auth/src/core/util/providers.ts diff --git a/packages-exp/auth-exp/src/core/util/resolver.ts b/packages/auth/src/core/util/resolver.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/resolver.ts rename to packages/auth/src/core/util/resolver.ts diff --git a/packages-exp/auth-exp/src/core/util/time.ts b/packages/auth/src/core/util/time.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/time.ts rename to packages/auth/src/core/util/time.ts diff --git a/packages-exp/auth-exp/src/core/util/validate_origin.test.ts b/packages/auth/src/core/util/validate_origin.test.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/validate_origin.test.ts rename to packages/auth/src/core/util/validate_origin.test.ts diff --git a/packages-exp/auth-exp/src/core/util/validate_origin.ts b/packages/auth/src/core/util/validate_origin.ts similarity index 100% rename from packages-exp/auth-exp/src/core/util/validate_origin.ts rename to packages/auth/src/core/util/validate_origin.ts diff --git a/packages-exp/auth-exp/src/core/util/version.test.ts b/packages/auth/src/core/util/version.test.ts similarity index 97% rename from packages-exp/auth-exp/src/core/util/version.test.ts rename to packages/auth/src/core/util/version.test.ts index 58228732844..0dccb5a98b3 100644 --- a/packages-exp/auth-exp/src/core/util/version.test.ts +++ b/packages/auth/src/core/util/version.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; import { expect } from 'chai'; import { ClientPlatform, _getClientVersion } from './version'; import { isNode } from '@firebase/util'; diff --git a/packages-exp/auth-exp/src/core/util/version.ts b/packages/auth/src/core/util/version.ts similarity index 97% rename from packages-exp/auth-exp/src/core/util/version.ts rename to packages/auth/src/core/util/version.ts index e8aecd48f17..d8345dadc91 100644 --- a/packages-exp/auth-exp/src/core/util/version.ts +++ b/packages/auth/src/core/util/version.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; import { _getBrowserName } from './browser'; import { getUA } from '@firebase/util'; diff --git a/packages/auth/src/debug.js b/packages/auth/src/debug.js deleted file mode 100644 index 8cbee245349..00000000000 --- a/packages/auth/src/debug.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Redefines various constants for internal testing and debugging. - */ - -/** @suppress {extraRequire} */ -goog.require('fireauth.iframeclient.IfcHandler'); - - -/** @suppress {const|duplicate} */ -fireauth.iframeclient.SCHEME = 'http'; -/** @type {?number} @suppress {const|duplicate} */ -fireauth.iframeclient.PORT_NUMBER = 8080; diff --git a/packages/auth/src/defines.js b/packages/auth/src/defines.js deleted file mode 100644 index a2b123904aa..00000000000 --- a/packages/auth/src/defines.js +++ /dev/null @@ -1,205 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines all common constants and enums used by firebase-auth. - */ - -goog.provide('fireauth.constants'); -goog.provide('fireauth.constants.AuthEventType'); - - -/** - * Enums for authentication operation types. - * @enum {string} - */ -fireauth.constants.OperationType = { - LINK: 'link', - REAUTHENTICATE: 'reauthenticate', - SIGN_IN: 'signIn' -}; - - -/** - * Events dispatched firebase.auth.Auth. - * @enum {string} - */ -fireauth.constants.AuthEventType = { - /** Dispatched when emulator config is changed. */ - EMULATOR_CONFIG_CHANGED: 'emulatorConfigChanged', - /** Dispatched when Firebase framework is changed. */ - FRAMEWORK_CHANGED: 'frameworkChanged', - /** Dispatched when language code is changed. */ - LANGUAGE_CODE_CHANGED: 'languageCodeChanged' -}; - - -/** - * Enums for all second factor types. - * @enum {string} - */ -fireauth.constants.SecondFactorType = { - PHONE: 'phone' -}; - - -/** - * The settings of an Auth endpoint. The fields are: - *
    - *
  • firebaseAuthEndpoint: defines the Firebase Auth backend endpoint for - * specified endpoint type.
  • - *
  • secureTokenEndpoint: defines the secure token backend endpoint for - * specified endpoint type.
  • - *
  • identityPlatformEndpoint: defines the Identity Platform backend endpoint - * for specified endpoint type.
  • - *
  • id: defines the endpoint identifier.
  • - *
- * @typedef {{ - * firebaseAuthEndpoint: string, - * secureTokenEndpoint: string, - * identityPlatformEndpoint: string, - * id: string - * }} - */ -fireauth.constants.EndpointSettings; - - -/** - * The different endpoints for Firebase Auth backend. - * @enum {!fireauth.constants.EndpointSettings} - */ -fireauth.constants.Endpoint = { - // TODO: this is no longer needed now that client endpoint migration is - // completed. - BOQ: { - firebaseAuthEndpoint: 'https://staging-identitytoolkit.sandbox.googleapi' + - 's.com/identitytoolkit/v3/relyingparty/', - secureTokenEndpoint: 'https://staging-securetoken.sandbox.googleapis.com' + - '/v1/token', - identityPlatformEndpoint: - 'https://staging-identitytoolkit.sandbox.googleapis.com/v2/', - id: 'b' - }, - PRODUCTION: { - firebaseAuthEndpoint: 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/', - secureTokenEndpoint: 'https://securetoken.googleapis.com/v1/token', - identityPlatformEndpoint: - 'https://identitytoolkit.googleapis.com/v2/', - id: 'p' - }, - STAGING: { - firebaseAuthEndpoint: 'https://staging-www.sandbox.googleapis.com/' + - 'identitytoolkit/v3/relyingparty/', - secureTokenEndpoint: 'https://staging-securetoken.sandbox.googleapis.com' + - '/v1/token', - identityPlatformEndpoint: - 'https://staging-identitytoolkit.sandbox.googleapis.com/v2/', - id: 's' - }, - TEST: { - firebaseAuthEndpoint: 'https://www-googleapis-test.sandbox.google.com/' + - 'identitytoolkit/v3/relyingparty/', - secureTokenEndpoint: 'https://test-securetoken.sandbox.googleapis.com/v1' + - '/token', - identityPlatformEndpoint: - 'https://test-identitytoolkit.sandbox.googleapis.com/v2/', - id: 't' - } -}; - - -/** - * Returns the endpoint specific RpcHandler configuration. - * @param {?string=} opt_id The identifier of the endpoint type if available. - * @return {?Object|undefined} The RpcHandler endpoint configuration object. - */ -fireauth.constants.getEndpointConfig = function(opt_id) { - for (var endpointKey in fireauth.constants.Endpoint) { - if (fireauth.constants.Endpoint[endpointKey].id === opt_id) { - var endpoint = fireauth.constants.Endpoint[endpointKey]; - return { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint, - 'identityPlatformEndpoint': endpoint.identityPlatformEndpoint - }; - } - } - return null; -}; - - -/** - * Returns the validated endpoint identifier. Undefined if the provided one is - * invalid. - * @param {?string=} opt_id The identifier of the endpoint type if available. - * @return {string|undefined} The validated endpoint ID. If not valid, - * undefined. - */ -fireauth.constants.getEndpointId = function(opt_id) { - if (opt_id && fireauth.constants.getEndpointConfig(opt_id)) { - return opt_id; - } - return undefined; -}; - - -/** @const {string|undefined} The current client endpoint. */ -fireauth.constants.clientEndpoint = fireauth.constants.getEndpointId('__EID__'); - - -/** @const {string} The required SAML provider ID prefix. */ -fireauth.constants.SAML_PREFIX = 'saml.'; - - -/** @const {string} The required OIDC provider ID prefix. */ -fireauth.constants.OIDC_PREFIX = 'oidc.'; - -/** - * The settings of an Auth emulator. The fields are: - *
    - *
  • url: defines the URL where the emulator is running.
  • - *
  • disableWarnings: if true, banner is not shown on the page.
  • - *
- * @typedef {{ - * url: string, - * disableWarnings: boolean, - * }} - */ -fireauth.constants.EmulatorSettings; - - -/** - * The (externally visible) emulator configuration, used for - * getEmulatorConfig(). The fields are: - *
    - *
  • protocol: the protocol used by the emulator (http or https).
  • - *
  • host: the host used to reach the emulator.
  • - *
  • port: the port used to reach the emulator.
  • - *
  • options: a list of options used to configure the SDK's interaction with - * the emulator.
  • - *
- * @typedef {{ - * protocol: string, - * host: string, - * port: (number|null), - * options: { - * disableWarnings: boolean, - * } - * }} - */ -fireauth.constants.EmulatorConfig; \ No newline at end of file diff --git a/packages/auth/src/deprecation.js b/packages/auth/src/deprecation.js deleted file mode 100644 index 0543dc0112f..00000000000 --- a/packages/auth/src/deprecation.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Provides utilities for displaying deprecation notices. - */ -goog.provide('fireauth.deprecation'); -goog.provide('fireauth.deprecation.Deprecations'); -goog.require('fireauth.util'); - - -/** - * An enum of valid notices to display. All deprecation notices must be in this - * enum. Deprecation messages should be unique and provide the full context - * of what is deprecated (e.g. the fully qualified path to a method). - * @enum {string} - */ -fireauth.deprecation.Deprecations = { - LINK_WITH_CREDENTIAL: 'firebase.User.prototype.linkAndRetrieveDataWithCrede' + - 'ntial is deprecated. Please use firebase.User.prototype.linkWithCreden' + - 'tial instead.', - REAUTH_WITH_CREDENTIAL: 'firebase.User.prototype.reauthenticateAndRetrieveD' + - 'ataWithCredential is deprecated. Please use firebase.User.prototype.re' + - 'authenticateWithCredential instead.', - SIGN_IN_WITH_CREDENTIAL: 'firebase.auth.Auth.prototype.signInAndRetrieveDat' + - 'aWithCredential is deprecated. Please use firebase.auth.Auth.prototype' + - '.signInWithCredential instead.' -}; - - -/** - * Keeps track of notices that were already displayed. - * @type {!Object} - * @private - */ -fireauth.deprecation.shownMessages_ = {}; - - -/** - * Logs a deprecation notice to the developer. - * @param {!fireauth.deprecation.Deprecations} message - */ -fireauth.deprecation.log = function(message) { - if (fireauth.deprecation.shownMessages_[message]) { - return; - } - fireauth.deprecation.shownMessages_[message] = true; - fireauth.util.consoleWarn(message); -}; - - -/** - * Resets the displayed deprecation notices. - */ -fireauth.deprecation.resetForTesting = function() { - fireauth.deprecation.shownMessages_ = - /** @type {!Object} */ ({}); -}; diff --git a/packages/auth/src/dynamiclink.js b/packages/auth/src/dynamiclink.js deleted file mode 100644 index dd167222dcf..00000000000 --- a/packages/auth/src/dynamiclink.js +++ /dev/null @@ -1,259 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the Firebase dynamic link constructor. - */ - -goog.provide('fireauth.DynamicLink'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.object'); -goog.require('fireauth.util'); -goog.require('goog.Uri'); - - -/** - * Dynamic link builder used to help build the FDL link to redirect to an app - * while passing some payload or error. - * @param {?string} fdlDomain The FDL domain. If none is available, custom - * scheme redirects are used. - * @param {!fireauth.DynamicLink.Platform} platform The FDL supported - * platform (Android or iOS). - * @param {string} appIdentifier The app identifier (iOS bundle ID or Android - * package name). - * @param {string} authDomain The Firebase application authDomain. - * @param {string} payload The FDL deep link content. - * @param {?string=} opt_clientId The optional OAuth client ID. - * @constructor - */ -fireauth.DynamicLink = function(fdlDomain, platform, appIdentifier, authDomain, - payload, opt_clientId) { - // The fallback error when the app is not installed on the device. - var defaultError = - new fireauth.AuthError(fireauth.authenum.Error.APP_NOT_INSTALLED); - /** @private {string} The fallback URL when the app is not installed. */ - this.fallbackUrl_ = 'https://' + authDomain + '/__/auth/handler?' + - 'firebaseError=' + encodeURIComponent(/** @type {string} */ ( - fireauth.util.stringifyJSON(defaultError.toPlainObject()))); - fireauth.object.setReadonlyProperty(this, 'fallbackUrl', this.fallbackUrl_); - /** @private {?string} The FDL domain if available. */ - this.fdlDomain_ = fdlDomain; - fireauth.object.setReadonlyProperty(this, 'fdlDomain', fdlDomain); - /** @private {!fireauth.DynamicLink.Platform} The FDL link platform. */ - this.platform_ = platform; - fireauth.object.setReadonlyProperty(this, 'platform', platform); - /** @private {string} The app identifier. */ - this.appIdentifier_ = appIdentifier; - fireauth.object.setReadonlyProperty(this, 'appIdentifier', appIdentifier); - /** @private {string} The Firebase application authDomain. */ - this.authDomain_ = authDomain; - fireauth.object.setReadonlyProperty(this, 'authDomain', authDomain); - /** @private {string} The FDL deep link content. */ - this.link_ = payload; - fireauth.object.setReadonlyProperty(this, 'payload', payload); - /** @private {?string} The application display name. */ - this.appName_ = null; - fireauth.object.setReadonlyProperty(this, 'appName', null); - /** @private {?string} The client ID if available. */ - this.clientId_ = opt_clientId || null; - fireauth.object.setReadonlyProperty(this, 'clientId', this.clientId_); -}; - - -/** - * Sets the app name for the current dynamic link. - * @param {?string|undefined} appName The app name typically displayed in an FDL - * button. - */ -fireauth.DynamicLink.prototype.setAppName = function(appName) { - this.appName_ = appName || null; - fireauth.object.setReadonlyProperty(this, 'appName', appName); -}; - - -/** - * Sets the dynamic link fallback URL overriding the default one. - * @param {string} fallbackUrl The dynamic link fallback URL. - */ -fireauth.DynamicLink.prototype.setFallbackUrl = function(fallbackUrl) { - this.fallbackUrl_ = fallbackUrl; - fireauth.object.setReadonlyProperty(this, 'fallbackUrl', fallbackUrl); -}; - - -/** - * Parses a dynamic link object from an automatic FDL redirect link. - * @param {string} url The URL string to parse and convert to a dynamic link. - * @return {?fireauth.DynamicLink} The corresponding dynamic link if applicable. - */ -fireauth.DynamicLink.fromURL = function(url) { - // This constructs the Dynamic link from the URL provided. - var uri = goog.Uri.parse(url); - var fdlDomain = uri.getParameterValue('fdlDomain'); - var platform = uri.getParameterValue('platform'); - var appIdentifier = uri.getParameterValue('appIdentifier'); - var authDomain = uri.getParameterValue('authDomain'); - var payload = uri.getParameterValue('link'); - var appName = uri.getParameterValue('appName'); - if (fdlDomain && platform && appIdentifier && authDomain && payload && - appName) { - var dl = new fireauth.DynamicLink( - /** @type {string} */ (fdlDomain), - /** @type {!fireauth.DynamicLink.Platform} */ (platform), - /** @type {string} */ (appIdentifier), - /** @type {string} */ (authDomain), - /** @type {string} */ (payload)); - dl.setAppName(appName); - return dl; - } - return null; -}; - - -/** - * @param {string} url The dynamic link URL. - * @return {string} The deep link embedded within the dynamic link. - */ -fireauth.DynamicLink.parseDeepLink = function(url) { - var uri = goog.Uri.parse(url); - var link = uri.getParameterValue('link'); - // Double link case (automatic redirect). - var doubleDeepLink = goog.Uri.parse(link).getParameterValue('link'); - // iOS custom scheme links. - var iOSdeepLink = uri.getParameterValue('deep_link_id'); - var iOSDoubledeepLink = goog.Uri.parse(iOSdeepLink).getParameterValue('link'); - var callbackUrl = - iOSDoubledeepLink || iOSdeepLink || doubleDeepLink || link || url; - return callbackUrl; -}; - - -/** - * The supported FDL platforms. - * @enum {string} - */ -fireauth.DynamicLink.Platform = { - ANDROID: 'android', - IOS: 'ios' -}; - - -/** - * Constructs the common FDL link base used for building the button link or the - * automatic redirect link. - * @param {string} fallbackUrl The fallback URL to use. - * @return {!goog.Uri} The partial URI of the FDL link used to build the final - * button link or the automatic redirect link. - * @private - */ -fireauth.DynamicLink.prototype.constructFdlBase_ = function(fallbackUrl) { - var uri = goog.Uri.create( - 'https', - null, - this.fdlDomain_, - null, - '/'); - if (this.platform_ == fireauth.DynamicLink.Platform.ANDROID) { - uri.setParameterValue('apn', this.appIdentifier_); - uri.setParameterValue('afl', fallbackUrl); - } else if (this.platform_ == fireauth.DynamicLink.Platform.IOS) { - uri.setParameterValue('ibi', this.appIdentifier_); - uri.setParameterValue('ifl', fallbackUrl); - } - return uri; -}; - - -/** - * Constructs the custom scheme URL. This is used when no FDL domain is - * available. - * @return {!goog.Uri} The uri of the dynamic link used to build the final - * button link or the automatic redirect link. - * @private - */ -fireauth.DynamicLink.prototype.constructCustomSchemeUrl_ = function() { - // This mimics the FDL custom scheme URL format. - var uri = goog.Uri.create( - this.clientId_ ? this.clientId_.split('.').reverse().join('.') : - this.appIdentifier_, - null, - // 'firebaseauth' is used in the app verification flow. - // 'google' is used for the Cordova iOS flow. - this.clientId_ ? 'firebaseauth' : 'google', - null, - '/link'); - uri.setParameterValue('deep_link_id', this.link_); - return uri; -}; - - -/** - * @param {boolean=} opt_isAutoRedirect Whether the link is an auto redirect - * link. - * @return {string} The generated dynamic link string. - * @override - */ -fireauth.DynamicLink.prototype.toString = function(opt_isAutoRedirect) { - // When FDL domain is not available, always returns the custom scheme URL. - if (!this.fdlDomain_) { - return this.constructCustomSchemeUrl_().toString(); - } - if (!!opt_isAutoRedirect) { - return this.generateAutomaticRedirectLink_(); - } - return this.generateButtonLink_(); -}; - - -/** - * @return {string} The final FDL button link. - * @private - */ -fireauth.DynamicLink.prototype.generateButtonLink_ = function() { - var fdlLink = this.constructFdlBase_(this.fallbackUrl_); - fdlLink.setParameterValue('link', this.link_); - return fdlLink.toString(); -}; - - -/** - * @return {string} The final FDL automatic redirect link. - * @private - */ -fireauth.DynamicLink.prototype.generateAutomaticRedirectLink_ = - function() { - var doubleDeeplink = goog.Uri.create( - 'https', - null, - this.authDomain_, - null, - '/__/auth/callback'); - doubleDeeplink.setParameterValue('fdlDomain', this.fdlDomain_); - doubleDeeplink.setParameterValue('platform', this.platform_); - doubleDeeplink.setParameterValue('appIdentifier', this.appIdentifier_); - doubleDeeplink.setParameterValue('authDomain', this.authDomain_); - doubleDeeplink.setParameterValue('link', this.link_); - doubleDeeplink.setParameterValue('appName', this.appName_ || ''); - // The fallback URL is the deep link itself. - // This is in case the link fails to be intercepted by the app, FDL will - // redirect to the fallback URL. - var fdlLink = this.constructFdlBase_(doubleDeeplink.toString()); - fdlLink.setParameterValue('link', doubleDeeplink.toString()); - return fdlLink.toString(); -}; diff --git a/packages/auth/src/error_auth.js b/packages/auth/src/error_auth.js deleted file mode 100644 index 84f6efff235..00000000000 --- a/packages/auth/src/error_auth.js +++ /dev/null @@ -1,474 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines developer-visible errors for Firebase Auth APIs. - */ - - -goog.provide('fireauth.AuthError'); -goog.provide('fireauth.authenum'); -goog.provide('fireauth.authenum.Error'); - - - -/** - * Error that can be returned to the developer. - * @param {!fireauth.authenum.Error} code The short error code. - * @param {?string=} message The human-readable message. - * @param {?Object=} serverResponse The raw server response. - * @constructor - * @extends {Error} - */ -fireauth.AuthError = function(code, message, serverResponse) { - this['code'] = fireauth.AuthError.ERROR_CODE_PREFIX + code; - this.message = message || fireauth.AuthError.MESSAGES_[code] || ''; - this.serverResponse = serverResponse || null; -}; -goog.inherits(fireauth.AuthError, Error); - - -/** - * @return {!Object} The plain object form of the error. - */ -fireauth.AuthError.prototype.toPlainObject = function() { - var obj = { - 'code': this['code'], - 'message': this.message - }; - if (this.serverResponse) { - obj['serverResponse'] = this.serverResponse; - } - return obj; -}; - - -/** - * @return {!Object} The plain object form of the error. This is used by - * JSON.toStringify() to return the stringified representation of the error; - * @override - */ -fireauth.AuthError.prototype.toJSON = function() { - // Return the plain object representation in case JSON.stringify is called on - // an auth error instance. - return this.toPlainObject(); -}; - - -/** - * @param {?Object|undefined} response The object response to convert to a - * fireauth.AuthError. - * @return {?fireauth.AuthError} The error representation of the response. - */ -fireauth.AuthError.fromPlainObject = function(response) { - var fullCode = response && response['code']; - if (fullCode) { - // Remove prefix from name. - var code = fullCode.substring( - fireauth.AuthError.ERROR_CODE_PREFIX.length); - return new fireauth.AuthError( - /** @type {!fireauth.authenum.Error} */ (code), - response['message'], - response['serverResponse']); - } - return null; -}; - - -/** - * Takes in an error and translates a specific error code to another one if - * found in the current error. - * @param {*} error The error thrown. - * @param {!fireauth.authenum.Error} fromCode The error code to translate from. - * @param {!fireauth.authenum.Error} toCode The error code to translate to. - * @return {*} The mapped error message. - */ -fireauth.AuthError.translateError = function(error, fromCode, toCode) { - if (error && - error['code'] && - error['code'] == fireauth.AuthError.ERROR_CODE_PREFIX + fromCode) { - // Translate the error to the new one. - return new fireauth.AuthError(toCode); - } - // Return the same error if the fromCode is not found. - return error; -}; - - -/** - * The error prefix for fireauth.Auth errors. - * @protected {string} - */ -fireauth.AuthError.ERROR_CODE_PREFIX = 'auth/'; - - -/** - * Developer facing Firebase Auth error codes. - * @enum {string} - */ -fireauth.authenum.Error = { - ADMIN_ONLY_OPERATION: 'admin-restricted-operation', - ARGUMENT_ERROR: 'argument-error', - APP_NOT_AUTHORIZED: 'app-not-authorized', - APP_NOT_INSTALLED: 'app-not-installed', - CAPTCHA_CHECK_FAILED: 'captcha-check-failed', - CODE_EXPIRED: 'code-expired', - CORDOVA_NOT_READY: 'cordova-not-ready', - CORS_UNSUPPORTED: 'cors-unsupported', - CREDENTIAL_ALREADY_IN_USE: 'credential-already-in-use', - CREDENTIAL_MISMATCH: 'custom-token-mismatch', - CREDENTIAL_TOO_OLD_LOGIN_AGAIN: 'requires-recent-login', - DYNAMIC_LINK_NOT_ACTIVATED: 'dynamic-link-not-activated', - EMAIL_CHANGE_NEEDS_VERIFICATION: 'email-change-needs-verification', - EMAIL_EXISTS: 'email-already-in-use', - EXPIRED_OOB_CODE: 'expired-action-code', - EXPIRED_POPUP_REQUEST: 'cancelled-popup-request', - INTERNAL_ERROR: 'internal-error', - INVALID_API_KEY: 'invalid-api-key', - INVALID_APP_CREDENTIAL: 'invalid-app-credential', - INVALID_APP_ID: 'invalid-app-id', - INVALID_AUTH: 'invalid-user-token', - INVALID_AUTH_EVENT: 'invalid-auth-event', - INVALID_CERT_HASH: 'invalid-cert-hash', - INVALID_CODE: 'invalid-verification-code', - INVALID_CONTINUE_URI: 'invalid-continue-uri', - INVALID_CORDOVA_CONFIGURATION: 'invalid-cordova-configuration', - INVALID_CUSTOM_TOKEN: 'invalid-custom-token', - INVALID_DYNAMIC_LINK_DOMAIN: 'invalid-dynamic-link-domain', - INVALID_EMAIL: 'invalid-email', - INVALID_IDP_RESPONSE: 'invalid-credential', - INVALID_MESSAGE_PAYLOAD: 'invalid-message-payload', - INVALID_MFA_PENDING_CREDENTIAL: 'invalid-multi-factor-session', - INVALID_OAUTH_CLIENT_ID: 'invalid-oauth-client-id', - INVALID_OAUTH_PROVIDER: 'invalid-oauth-provider', - INVALID_OOB_CODE: 'invalid-action-code', - INVALID_ORIGIN: 'unauthorized-domain', - INVALID_PASSWORD: 'wrong-password', - INVALID_PERSISTENCE: 'invalid-persistence-type', - INVALID_PHONE_NUMBER: 'invalid-phone-number', - INVALID_PROVIDER_ID: 'invalid-provider-id', - INVALID_RECIPIENT_EMAIL: 'invalid-recipient-email', - INVALID_SENDER: 'invalid-sender', - INVALID_SESSION_INFO: 'invalid-verification-id', - INVALID_TENANT_ID: 'invalid-tenant-id', - MFA_ENROLLMENT_NOT_FOUND: 'multi-factor-info-not-found', - MFA_REQUIRED: 'multi-factor-auth-required', - MISSING_ANDROID_PACKAGE_NAME: 'missing-android-pkg-name', - MISSING_APP_CREDENTIAL: 'missing-app-credential', - MISSING_AUTH_DOMAIN: 'auth-domain-config-required', - MISSING_CODE: 'missing-verification-code', - MISSING_CONTINUE_URI: 'missing-continue-uri', - MISSING_IFRAME_START: 'missing-iframe-start', - MISSING_IOS_BUNDLE_ID: 'missing-ios-bundle-id', - MISSING_MFA_ENROLLMENT_ID: 'missing-multi-factor-info', - MISSING_MFA_PENDING_CREDENTIAL: 'missing-multi-factor-session', - MISSING_OR_INVALID_NONCE: 'missing-or-invalid-nonce', - MISSING_PHONE_NUMBER: 'missing-phone-number', - MISSING_SESSION_INFO: 'missing-verification-id', - MODULE_DESTROYED: 'app-deleted', - NEED_CONFIRMATION: 'account-exists-with-different-credential', - NETWORK_REQUEST_FAILED: 'network-request-failed', - NULL_USER: 'null-user', - NO_AUTH_EVENT: 'no-auth-event', - NO_SUCH_PROVIDER: 'no-such-provider', - OPERATION_NOT_ALLOWED: 'operation-not-allowed', - OPERATION_NOT_SUPPORTED: 'operation-not-supported-in-this-environment', - POPUP_BLOCKED: 'popup-blocked', - POPUP_CLOSED_BY_USER: 'popup-closed-by-user', - PROVIDER_ALREADY_LINKED: 'provider-already-linked', - QUOTA_EXCEEDED: 'quota-exceeded', - REDIRECT_CANCELLED_BY_USER: 'redirect-cancelled-by-user', - REDIRECT_OPERATION_PENDING: 'redirect-operation-pending', - REJECTED_CREDENTIAL: 'rejected-credential', - SECOND_FACTOR_EXISTS: 'second-factor-already-in-use', - SECOND_FACTOR_LIMIT_EXCEEDED: 'maximum-second-factor-count-exceeded', - TENANT_ID_MISMATCH: 'tenant-id-mismatch', - TIMEOUT: 'timeout', - TOKEN_EXPIRED: 'user-token-expired', - TOO_MANY_ATTEMPTS_TRY_LATER: 'too-many-requests', - UNAUTHORIZED_DOMAIN: 'unauthorized-continue-uri', - UNSUPPORTED_FIRST_FACTOR: 'unsupported-first-factor', - UNSUPPORTED_PERSISTENCE: 'unsupported-persistence-type', - UNSUPPORTED_TENANT_OPERATION: 'unsupported-tenant-operation', - UNVERIFIED_EMAIL: 'unverified-email', - USER_CANCELLED: 'user-cancelled', - USER_DELETED: 'user-not-found', - USER_DISABLED: 'user-disabled', - USER_MISMATCH: 'user-mismatch', - USER_SIGNED_OUT: 'user-signed-out', - WEAK_PASSWORD: 'weak-password', - WEB_STORAGE_UNSUPPORTED: 'web-storage-unsupported' -}; - - -/** - * Map from developer error codes to human-readable error messages. - * @private {!Object} - */ -fireauth.AuthError.MESSAGES_ = {}; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.ADMIN_ONLY_OPERATION] = - 'This operation is restricted to administrators only.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.ARGUMENT_ERROR] = ''; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.APP_NOT_AUTHORIZED] = - 'This app, identified by the domain where it\'s hosted, is not ' + - 'authorized to use Firebase Authentication with the provided API key. ' + - 'Review your key configuration in the Google API console.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.APP_NOT_INSTALLED] = - 'The requested mobile application corresponding to the identifier (' + - 'Android package name or iOS bundle ID) provided is not installed on ' + - 'this device.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.CAPTCHA_CHECK_FAILED] = - 'The reCAPTCHA response token provided is either invalid, expired, ' + - 'already used or the domain associated with it does not match the list ' + - 'of whitelisted domains.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.CODE_EXPIRED] = - 'The SMS code has expired. Please re-send the verification code to try ' + - 'again.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.CORDOVA_NOT_READY] = - 'Cordova framework is not ready.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.CORS_UNSUPPORTED] = - 'This browser is not supported.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE] = - 'This credential is already associated with a different user account.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.CREDENTIAL_MISMATCH] = - 'The custom token corresponds to a different audience.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.CREDENTIAL_TOO_OLD_LOGIN_AGAIN] = - 'This operation is sensitive and requires recent authentication. Log in ' + - 'again before retrying this request.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.DYNAMIC_LINK_NOT_ACTIVATED] = 'Please activate ' + - 'Dynamic Links in the Firebase Console and agree to the terms and ' + - 'conditions.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.EMAIL_CHANGE_NEEDS_VERIFICATION] = - 'Multi-factor users must always have a verified email.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.EMAIL_EXISTS] = - 'The email address is already in use by another account.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.EXPIRED_OOB_CODE] = - 'The action code has expired. '; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.EXPIRED_POPUP_REQUEST] = - 'This operation has been cancelled due to another conflicting popup ' + - 'being opened.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INTERNAL_ERROR] = - 'An internal error has occurred.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_APP_CREDENTIAL] = - 'The phone verification request contains an invalid application verifier.' + - ' The reCAPTCHA token response is either invalid or expired.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_APP_ID] = - 'The mobile app identifier is not registed for the current project.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_AUTH] = - 'This user\'s credential isn\'t valid for this project. This can happen ' + - 'if the user\'s token has been tampered with, or if the user isn\'t for ' + - 'the project associated with this API key.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_AUTH_EVENT] = - 'An internal error has occurred.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_CODE] = - 'The SMS verification code used to create the phone auth credential is ' + - 'invalid. Please resend the verification code sms and be sure to use the ' + - 'verification code provided by the user.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_CONTINUE_URI] = - 'The continue URL provided in the request is invalid.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION] = 'The following' + - ' Cordova plugins must be installed to enable OAuth sign-in: ' + - 'cordova-plugin-buildinfo, cordova-universal-links-plugin, ' + - 'cordova-plugin-browsertab, cordova-plugin-inappbrowser and ' + - 'cordova-plugin-customurlscheme.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_CUSTOM_TOKEN] = - 'The custom token format is incorrect. Please check the documentation.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.INVALID_DYNAMIC_LINK_DOMAIN] = 'The provided ' + - 'dynamic link domain is not configured or authorized for the current ' + - 'project.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_EMAIL] = - 'The email address is badly formatted.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_API_KEY] = - 'Your API key is invalid, please check you have copied it correctly.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_CERT_HASH] = - 'The SHA-1 certificate hash provided is invalid.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_IDP_RESPONSE] = - 'The supplied auth credential is malformed or has expired.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_MESSAGE_PAYLOAD] = - 'The email template corresponding to this action contains invalid charac' + - 'ters in its message. Please fix by going to the Auth email templates se' + - 'ction in the Firebase Console.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.INVALID_MFA_PENDING_CREDENTIAL] = - 'The request does not contain a valid proof of first factor successful ' + - 'sign-in.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_OAUTH_PROVIDER] = - 'EmailAuthProvider is not supported for this operation. This operation ' + - 'only supports OAuth providers.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_OAUTH_CLIENT_ID] = - 'The OAuth client ID provided is either invalid or does not match the ' + - 'specified API key.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_ORIGIN] = - 'This domain is not authorized for OAuth operations for your Firebase ' + - 'project. Edit the list of authorized domains from the Firebase console.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_OOB_CODE] = - 'The action code is invalid. This can happen if the code is malformed, ' + - 'expired, or has already been used.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_PASSWORD] = - 'The password is invalid or the user does not have a password.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_PERSISTENCE] = - 'The specified persistence type is invalid. It can only be local, ' + - 'session or none.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_PHONE_NUMBER] = - 'The format of the phone number provided is incorrect. Please enter the ' + - 'phone number in a format that can be parsed into E.164 format. E.164 ' + - 'phone numbers are written in the format [+][country code][subscriber ' + - 'number including area code].'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_PROVIDER_ID] = - 'The specified provider ID is invalid.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_RECIPIENT_EMAIL] = - 'The email corresponding to this action failed to send as the provided ' + - 'recipient email address is invalid.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_SENDER] = - 'The email template corresponding to this action contains an invalid sen' + - 'der email or name. Please fix by going to the Auth email templates sect' + - 'ion in the Firebase Console.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_SESSION_INFO] = - 'The verification ID used to create the phone auth credential is invalid.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_TENANT_ID] = - 'The Auth instance\'s tenant ID is invalid.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.MFA_ENROLLMENT_NOT_FOUND] = 'The user does not ' + - 'have a second factor matching the identifier provided.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MFA_REQUIRED] = - 'Proof of ownership of a second factor is required to complete sign-in.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.MISSING_ANDROID_PACKAGE_NAME] = 'An Android ' + - 'Package Name must be provided if the Android App is required to be ' + - 'installed.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_AUTH_DOMAIN] = - 'Be sure to include authDomain when calling firebase.initializeApp(), ' + - 'by following the instructions in the Firebase console.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_APP_CREDENTIAL] = - 'The phone verification request is missing an application verifier ' + - 'assertion. A reCAPTCHA response token needs to be provided.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_CODE] = - 'The phone auth credential was created with an empty SMS verification ' + - 'code.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_CONTINUE_URI] = - 'A continue URL must be provided in the request.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_IFRAME_START] = - 'An internal error has occurred.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_IOS_BUNDLE_ID] = - 'An iOS Bundle ID must be provided if an App Store ID is provided.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.MISSING_MFA_ENROLLMENT_ID] = - 'No second factor identifier is provided.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.MISSING_MFA_PENDING_CREDENTIAL] = - 'The request is missing proof of first factor successful sign-in.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_OR_INVALID_NONCE] = - 'The request does not contain a valid nonce. This can occur if the ' + - 'SHA-256 hash of the provided raw nonce does not match the hashed nonce ' + - 'in the ID token payload.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_PHONE_NUMBER] = - 'To send verification codes, provide a phone number for the recipient.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MISSING_SESSION_INFO] = - 'The phone auth credential was created with an empty verification ID.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MODULE_DESTROYED] = - 'This instance of FirebaseApp has been deleted.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.NEED_CONFIRMATION] = - 'An account already exists with the same email address but different ' + - 'sign-in credentials. Sign in using a provider associated with this ' + - 'email address.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.NETWORK_REQUEST_FAILED] = - 'A network error (such as timeout, interrupted connection or ' + - 'unreachable host) has occurred.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.NO_AUTH_EVENT] = - 'An internal error has occurred.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.NO_SUCH_PROVIDER] = - 'User was not linked to an account with the given provider.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.NULL_USER] = - 'A null user object was provided as the argument for an operation which ' + - 'requires a non-null user object.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.OPERATION_NOT_ALLOWED] = - 'The given sign-in provider is disabled for this Firebase project. ' + - 'Enable it in the Firebase console, under the sign-in method tab of the ' + - 'Auth section.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.OPERATION_NOT_SUPPORTED] = - 'This operation is not supported in the environment this application is ' + - 'running on. "location.protocol" must be http, https or chrome-extension' + - ' and web storage must be enabled.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.POPUP_BLOCKED] = - 'Unable to establish a connection with the popup. It may have been ' + - 'blocked by the browser.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.POPUP_CLOSED_BY_USER] = - 'The popup has been closed by the user before finalizing the operation.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.PROVIDER_ALREADY_LINKED] = - 'User can only be linked to one identity for the given provider.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.QUOTA_EXCEEDED] = - 'The project\'s quota for this operation has been exceeded.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.REDIRECT_CANCELLED_BY_USER] = - 'The redirect operation has been cancelled by the user before finalizing.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.REDIRECT_OPERATION_PENDING] = - 'A redirect sign-in operation is already pending.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.REJECTED_CREDENTIAL] = - 'The request contains malformed or mismatching credentials.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.SECOND_FACTOR_EXISTS] = - 'The second factor is already enrolled on this account.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.SECOND_FACTOR_LIMIT_EXCEEDED] = - 'The maximum allowed number of second factors on a user has been exceeded.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.TENANT_ID_MISMATCH] = - 'The provided tenant ID does not match the Auth instance\'s tenant ID'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.TIMEOUT] = - 'The operation has timed out.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.TOKEN_EXPIRED] = - 'The user\'s credential is no longer valid. The user must sign in again.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.TOO_MANY_ATTEMPTS_TRY_LATER] = - 'We have blocked all requests from this device due to unusual activity. ' + - 'Try again later.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.UNAUTHORIZED_DOMAIN] = - 'The domain of the continue URL is not whitelisted. Please whitelist ' + - 'the domain in the Firebase console.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.UNSUPPORTED_FIRST_FACTOR] = 'Enrolling a second ' + - 'factor or signing in with a multi-factor account requires sign-in with ' + - 'a supported first factor.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.UNSUPPORTED_PERSISTENCE] = - 'The current environment does not support the specified persistence type.'; -fireauth.AuthError.MESSAGES_[ - fireauth.authenum.Error.UNSUPPORTED_TENANT_OPERATION] = - 'This operation is not supported in a multi-tenant context.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.UNVERIFIED_EMAIL] = - 'The operation requires a verified email.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.USER_CANCELLED] = - 'The user did not grant your application the permissions it requested.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.USER_DELETED] = - 'There is no user record corresponding to this identifier. The user may ' + - 'have been deleted.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.USER_DISABLED] = - 'The user account has been disabled by an administrator.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.USER_MISMATCH] = - 'The supplied credentials do not correspond to the previously signed in ' + - 'user.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.USER_SIGNED_OUT] = ''; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.WEAK_PASSWORD] = - 'The password must be 6 characters long or more.'; -fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED] = - 'This browser is not supported or 3rd party cookies and data may be ' + - 'disabled.'; diff --git a/packages/auth/src/error_invalidorigin.js b/packages/auth/src/error_invalidorigin.js deleted file mode 100644 index edd97585921..00000000000 --- a/packages/auth/src/error_invalidorigin.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the invalid origin error, a subclass of - * fireauth.AuthError. - */ - - -goog.provide('fireauth.InvalidOriginError'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('goog.Uri'); -goog.require('goog.string'); - - - -/** - * Invalid origin error that can be returned to the developer. - * @param {string} origin The invalid domain name. - * @constructor - * @extends {fireauth.AuthError} - */ -fireauth.InvalidOriginError = function(origin) { - var code = fireauth.authenum.Error.INVALID_ORIGIN; - var message = undefined; - var uri = goog.Uri.parse(origin); - // Get domain. - var domain = uri.getDomain(); - // Get scheme. - var scheme = uri.getScheme(); - // Only http, https and chrome-extension currently supported. - if (scheme == 'chrome-extension') { - // Chrome extension whitelisting. - // Replace chrome-extension://CHROME_EXT_ID in error message template. - message = goog.string.subs( - fireauth.InvalidOriginError.CHROME_EXTENSION_MESSAGE_TEMPLATE_, - domain); - } else if (scheme == 'http' || scheme == 'https') { - // Replace domain in error message template. - message = goog.string.subs( - fireauth.InvalidOriginError.HTTP_MESSAGE_TEMPLATE_, - domain); - } else { - // Throw operation not supported when non http, https or Chrome extension - // protocol. - code = fireauth.authenum.Error.OPERATION_NOT_SUPPORTED; - } - fireauth.InvalidOriginError.base(this, 'constructor', code, message); -}; -goog.inherits(fireauth.InvalidOriginError, fireauth.AuthError); - - -/** @private @const {string} The http invalid origin message template. */ -fireauth.InvalidOriginError.HTTP_MESSAGE_TEMPLATE_ = 'This domain (%s) is no' + - 't authorized to run this operation. Add it to the OAuth redirect domain' + - 's list in the Firebase console -> Auth section -> Sign in method tab.'; - - -/** - * @private @const {string} The Chrome extension invalid origin message - * template. - */ -fireauth.InvalidOriginError.CHROME_EXTENSION_MESSAGE_TEMPLATE_ = 'This chrom' + - 'e extension ID (chrome-extension://%s) is not authorized to run this op' + - 'eration. Add it to the OAuth redirect domains list in the Firebase cons' + - 'ole -> Auth section -> Sign in method tab.'; diff --git a/packages/auth/src/error_withcredential.js b/packages/auth/src/error_withcredential.js deleted file mode 100644 index 5b7759af88a..00000000000 --- a/packages/auth/src/error_withcredential.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the Auth errors that include emails and an Auth - * credential, a subclass of fireauth.AuthError. - */ - - -goog.provide('fireauth.AuthErrorWithCredential'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthProvider'); -goog.require('fireauth.object'); -goog.require('goog.object'); - - -/** - * Error with email and credential that can be returned to the developer. - * @param {fireauth.authenum.Error} code The error code. - * @param {?fireauth.AuthErrorWithCredential.CredentialInfo=} opt_credentialInfo - * Additional credential information to associate with the error. - * @param {string=} opt_message The human-readable message. - * @constructor - * @extends {fireauth.AuthError} - */ -fireauth.AuthErrorWithCredential = - function(code, opt_credentialInfo, opt_message) { - fireauth.AuthErrorWithCredential.base( - this, 'constructor', code, opt_message); - var credentialInfo = opt_credentialInfo || {}; - - // These properties are public. - if (credentialInfo.email) { - fireauth.object.setReadonlyProperty(this, 'email', credentialInfo.email); - } - if (credentialInfo.phoneNumber) { - fireauth.object.setReadonlyProperty(this, 'phoneNumber', - credentialInfo.phoneNumber); - } - if (credentialInfo.credential) { - fireauth.object.setReadonlyProperty(this, 'credential', - credentialInfo.credential); - } - if (credentialInfo.tenantId) { - fireauth.object.setReadonlyProperty(this, 'tenantId', - credentialInfo.tenantId); - } -}; -goog.inherits(fireauth.AuthErrorWithCredential, fireauth.AuthError); - - -/** - * Additional credential information to associate with an error, so that the - * user does not have to execute the Auth flow again on linking errors. - * @typedef {{ - * email: (?string|undefined), - * phoneNumber: (?string|undefined), - * credential: (?fireauth.AuthCredential|undefined), - * tenantId: (?string|undefined), - * }} - */ -fireauth.AuthErrorWithCredential.CredentialInfo; - - -/** - * @return {!Object} The plain object form of the error. - * @override - */ -fireauth.AuthErrorWithCredential.prototype.toPlainObject = function() { - var obj = { - 'code': this['code'], - 'message': this.message - }; - if (this['email']) { - obj['email'] = this['email']; - } - if (this['phoneNumber']) { - obj['phoneNumber'] = this['phoneNumber']; - } - if (this['tenantId']) { - obj['tenantId'] = this['tenantId']; - } - - var credential = this['credential'] && this['credential'].toPlainObject(); - if (credential){ - goog.object.extend(obj, credential); - } - return obj; -}; - - -/** - * @return {!Object} The plain object form of the error. This is used by - * JSON.toStringify() to return the stringified representation of the error; - * @override - */ -fireauth.AuthErrorWithCredential.prototype.toJSON = function() { - // Return the plain object representation in case JSON.stringify is called on - // an Auth error instance. - return this.toPlainObject(); -}; - - -/** - * @param {?Object|undefined} response The object response to convert to a - * fireauth.AuthErrorWithCredential. - * @return {?fireauth.AuthError} The error representation of the response. - */ -fireauth.AuthErrorWithCredential.fromPlainObject = function(response) { - // Code included. - if (response['code']) { - var code = response['code'] || ''; - // Remove prefix from name if available. - if (code.indexOf(fireauth.AuthError.ERROR_CODE_PREFIX) == 0) { - code = code.substring(fireauth.AuthError.ERROR_CODE_PREFIX.length); - } - - // Credentials and tenant ID in response. - var credentialInfo = { - credential: fireauth.AuthProvider.getCredentialFromResponse(response), - tenantId: response['tenantId'] - }; - if (response['email']) { - credentialInfo.email = response['email']; - } else if (response['phoneNumber']) { - credentialInfo.phoneNumber = response['phoneNumber']; - } else if (!credentialInfo.credential) { - // Neither email, phone number or credentials are set; return a generic - // error. - return new fireauth.AuthError(code, response['message'] || undefined); - } - - return new fireauth.AuthErrorWithCredential(code, credentialInfo, - response['message']); - } - // No error or invalid response. - return null; -}; diff --git a/packages/auth/src/exports_auth.js b/packages/auth/src/exports_auth.js deleted file mode 100644 index 9f4ddc000de..00000000000 --- a/packages/auth/src/exports_auth.js +++ /dev/null @@ -1,804 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.exports'); - -goog.require('fireauth.ActionCodeInfo'); -goog.require('fireauth.ActionCodeURL'); -goog.require('fireauth.Auth'); -goog.require('fireauth.AuthCredential'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthErrorWithCredential'); -goog.require('fireauth.AuthSettings'); -goog.require('fireauth.AuthUser'); -goog.require('fireauth.ConfirmationResult'); -goog.require('fireauth.EmailAuthCredential'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.FacebookAuthProvider'); -goog.require('fireauth.GRecaptchaMockFactory'); -goog.require('fireauth.GithubAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.InvalidOriginError'); -goog.require('fireauth.MultiFactorError'); -goog.require('fireauth.MultiFactorResolver'); -goog.require('fireauth.MultiFactorUser'); -goog.require('fireauth.OAuthCredential'); -goog.require('fireauth.OAuthProvider'); -goog.require('fireauth.PhoneAuthCredential'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.PhoneMultiFactorGenerator'); -goog.require('fireauth.RecaptchaVerifier'); -goog.require('fireauth.SAMLAuthCredential'); -goog.require('fireauth.SAMLAuthProvider'); -goog.require('fireauth.TwitterAuthProvider'); -goog.require('fireauth.args'); -goog.require('fireauth.authStorage.Persistence'); -goog.require('fireauth.exportlib'); -goog.require('fireauth.grecaptcha'); -goog.require('fireauth.idp.ProviderId'); -goog.require('goog.Promise'); - -/** @define {string} */ -const AUTH_NPM_PACKAGE_VERSION = ''; - -fireauth.exportlib.exportPrototypeMethods( - fireauth.Auth.prototype, { - applyActionCode: { - name: 'applyActionCode', - args: [fireauth.args.string('code')] - }, - checkActionCode: { - name: 'checkActionCode', - args: [fireauth.args.string('code')] - }, - confirmPasswordReset: { - name: 'confirmPasswordReset', - args: [ - fireauth.args.string('code'), - fireauth.args.string('newPassword') - ] - }, - createUserWithEmailAndPassword: { - name: 'createUserWithEmailAndPassword', - args: [fireauth.args.string('email'), fireauth.args.string('password')] - }, - fetchSignInMethodsForEmail: { - name: 'fetchSignInMethodsForEmail', - args: [fireauth.args.string('email')] - }, - getRedirectResult: { - name: 'getRedirectResult', - args: [] - }, - isSignInWithEmailLink: { - name: 'isSignInWithEmailLink', - args: [fireauth.args.string('emailLink')] - }, - onAuthStateChanged: { - name: 'onAuthStateChanged', - args: [ - fireauth.args.or( - fireauth.args.object(), - fireauth.args.func(), - 'nextOrObserver'), - fireauth.args.func('opt_error', true), - fireauth.args.func('opt_completed', true) - ] - }, - onIdTokenChanged: { - name: 'onIdTokenChanged', - args: [ - fireauth.args.or( - fireauth.args.object(), - fireauth.args.func(), - 'nextOrObserver'), - fireauth.args.func('opt_error', true), - fireauth.args.func('opt_completed', true) - ] - }, - sendPasswordResetEmail: { - name: 'sendPasswordResetEmail', - args: [ - fireauth.args.string('email'), - fireauth.args.or( - fireauth.args.object('opt_actionCodeSettings', true), - fireauth.args.null(null, true), - 'opt_actionCodeSettings', - true) - ] - }, - sendSignInLinkToEmail: { - name: 'sendSignInLinkToEmail', - args: [ - fireauth.args.string('email'), - fireauth.args.object('actionCodeSettings') - ] - }, - setPersistence: { - name: 'setPersistence', - args: [fireauth.args.string('persistence')] - }, - signInAndRetrieveDataWithCredential: { - name: 'signInAndRetrieveDataWithCredential', - args: [fireauth.args.authCredential()] - }, - signInAnonymously: { - name: 'signInAnonymously', - args: [] - }, - signInWithCredential: { - name: 'signInWithCredential', - args: [fireauth.args.authCredential()] - }, - signInWithCustomToken: { - name: 'signInWithCustomToken', - args: [fireauth.args.string('token')] - }, - signInWithEmailAndPassword: { - name: 'signInWithEmailAndPassword', - args: [fireauth.args.string('email'), fireauth.args.string('password')] - }, - signInWithEmailLink: { - name: 'signInWithEmailLink', - args: [ - fireauth.args.string('email'), fireauth.args.string('emailLink', true) - ] - }, - signInWithPhoneNumber: { - name: 'signInWithPhoneNumber', - args: [ - fireauth.args.string('phoneNumber'), - fireauth.args.applicationVerifier() - ] - }, - signInWithPopup: { - name: 'signInWithPopup', - args: [fireauth.args.authProvider()] - }, - signInWithRedirect: { - name: 'signInWithRedirect', - args: [fireauth.args.authProvider()] - }, - updateCurrentUser: { - name: 'updateCurrentUser', - args: [ - fireauth.args.or( - fireauth.args.firebaseUser(), - fireauth.args.null(), - 'user') - ] - }, - signOut: { - name: 'signOut', - args: [] - }, - toJSON: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - }, - useDeviceLanguage: { - name: 'useDeviceLanguage', - args: [] - }, - useEmulator: { - name: 'useEmulator', - args: [ - fireauth.args.string('url'), - fireauth.args.object('options', true) - ] - }, - verifyPasswordResetCode: { - name: 'verifyPasswordResetCode', - args: [fireauth.args.string('code')] - } - }); - -fireauth.exportlib.exportPrototypeProperties( - fireauth.Auth.prototype, { - 'lc': { - name: 'languageCode', - arg: fireauth.args.or( - fireauth.args.string(), - fireauth.args.null(), - 'languageCode') - }, - 'ti': { - name: 'tenantId', - arg: fireauth.args.or( - fireauth.args.string(), - fireauth.args.null(), - 'tenantId') - }, - // emulatorConfig is omitted here as it is readonly and therefore does not - // need argument validation. - }); - -// Exports firebase.auth.Auth.Persistence. -fireauth.Auth['Persistence'] = fireauth.authStorage.Persistence; -fireauth.Auth['Persistence']['LOCAL'] = fireauth.authStorage.Persistence.LOCAL; -fireauth.Auth['Persistence']['SESSION'] = - fireauth.authStorage.Persistence.SESSION; -fireauth.Auth['Persistence']['NONE'] = fireauth.authStorage.Persistence.NONE; - - -fireauth.exportlib.exportPrototypeMethods( - fireauth.AuthUser.prototype, { - 'delete': { - name: 'delete', - args: [] - }, - getIdTokenResult: { - name: 'getIdTokenResult', - args: [fireauth.args.bool('opt_forceRefresh', true)] - }, - getIdToken: { - name: 'getIdToken', - args: [fireauth.args.bool('opt_forceRefresh', true)] - }, - linkAndRetrieveDataWithCredential: { - name: 'linkAndRetrieveDataWithCredential', - args: [fireauth.args.authCredential()] - }, - linkWithCredential: { - name: 'linkWithCredential', - args: [fireauth.args.authCredential()] - }, - linkWithPhoneNumber: { - name: 'linkWithPhoneNumber', - args: [ - fireauth.args.string('phoneNumber'), - fireauth.args.applicationVerifier() - ] - }, - linkWithPopup: { - name: 'linkWithPopup', - args: [fireauth.args.authProvider()] - }, - linkWithRedirect: { - name: 'linkWithRedirect', - args: [fireauth.args.authProvider()] - }, - reauthenticateAndRetrieveDataWithCredential: { - name: 'reauthenticateAndRetrieveDataWithCredential', - args: [fireauth.args.authCredential()] - }, - reauthenticateWithCredential: { - name: 'reauthenticateWithCredential', - args: [fireauth.args.authCredential()] - }, - reauthenticateWithPhoneNumber: { - name: 'reauthenticateWithPhoneNumber', - args: [ - fireauth.args.string('phoneNumber'), - fireauth.args.applicationVerifier() - ] - }, - reauthenticateWithPopup: { - name: 'reauthenticateWithPopup', - args: [fireauth.args.authProvider()] - }, - reauthenticateWithRedirect: { - name: 'reauthenticateWithRedirect', - args: [fireauth.args.authProvider()] - }, - reload: { - name: 'reload', - args: [] - }, - sendEmailVerification: { - name: 'sendEmailVerification', - args: [ - fireauth.args.or( - fireauth.args.object('opt_actionCodeSettings', true), - fireauth.args.null(null, true), - 'opt_actionCodeSettings', - true) - ] - }, - toJSON: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - }, - unlink: { - name: 'unlink', - args: [fireauth.args.string('provider')] - }, - updateEmail: { - name: 'updateEmail', - args: [fireauth.args.string('email')] - }, - updatePassword: { - name: 'updatePassword', - args: [fireauth.args.string('password')] - }, - updatePhoneNumber: { - name: 'updatePhoneNumber', - args: [fireauth.args.authCredential(fireauth.idp.ProviderId.PHONE)] - }, - updateProfile: { - name: 'updateProfile', - args: [fireauth.args.object('profile')] - }, - verifyBeforeUpdateEmail: { - name: 'verifyBeforeUpdateEmail', - args: [ - fireauth.args.string('email'), - fireauth.args.or( - fireauth.args.object('opt_actionCodeSettings', true), - fireauth.args.null(null, true), - 'opt_actionCodeSettings', - true) - ] - } - }); - -// Ensure internal grecaptcha mock API do not get obfuscated. -fireauth.exportlib.exportPrototypeMethods( - fireauth.GRecaptchaMockFactory.prototype, { - execute: { - name: 'execute' - }, - render: { - name: 'render' - }, - reset: { - name: 'reset' - }, - getResponse: { - name: 'getResponse' - } - }); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.grecaptcha.prototype, { - execute: { - name: 'execute' - }, - render: { - name: 'render' - }, - reset: { - name: 'reset' - }, - getResponse: { - name: 'getResponse' - } - }); - -fireauth.exportlib.exportPrototypeMethods( - goog.Promise.prototype, { - thenAlways: { - name: 'finally' - }, - thenCatch: { - name: 'catch' - }, - then: { - name: 'then' - } - }); - -fireauth.exportlib.exportPrototypeProperties( - fireauth.AuthSettings.prototype, { - 'appVerificationDisabled': { - name: 'appVerificationDisabledForTesting', - arg: fireauth.args.bool('appVerificationDisabledForTesting') - } - }); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.ConfirmationResult.prototype, { - confirm: { - name: 'confirm', - args: [ - fireauth.args.string('verificationCode') - ] - } - }); - -fireauth.exportlib.exportFunction( - fireauth.AuthCredential, 'fromJSON', - fireauth.AuthCredential.fromPlainObject, [ - fireauth.args.or(fireauth.args.string(), fireauth.args.object(), 'json') - ]); - -fireauth.exportlib.exportFunction( - fireauth.EmailAuthProvider, 'credential', - fireauth.EmailAuthProvider.credential, [ - fireauth.args.string('email'), - fireauth.args.string('password') - ]); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.EmailAuthCredential.prototype, { - toPlainObject: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - } - }); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.FacebookAuthProvider.prototype, { - addScope: { - name: 'addScope', - args: [fireauth.args.string('scope')] - }, - setCustomParameters: { - name: 'setCustomParameters', - args: [fireauth.args.object('customOAuthParameters')] - } - }); -fireauth.exportlib.exportFunction( - fireauth.FacebookAuthProvider, 'credential', - fireauth.FacebookAuthProvider.credential, [ - fireauth.args.or(fireauth.args.string(), fireauth.args.object(), - 'token') - ]); -fireauth.exportlib.exportFunction( - fireauth.EmailAuthProvider, 'credentialWithLink', - fireauth.EmailAuthProvider.credentialWithLink, [ - fireauth.args.string('email'), - fireauth.args.string('emailLink') - ]); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.GithubAuthProvider.prototype, { - addScope: { - name: 'addScope', - args: [fireauth.args.string('scope')] - }, - setCustomParameters: { - name: 'setCustomParameters', - args: [fireauth.args.object('customOAuthParameters')] - } - }); -fireauth.exportlib.exportFunction( - fireauth.GithubAuthProvider, 'credential', - fireauth.GithubAuthProvider.credential, [ - fireauth.args.or(fireauth.args.string(), fireauth.args.object(), - 'token') - ]); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.GoogleAuthProvider.prototype, { - addScope: { - name: 'addScope', - args: [fireauth.args.string('scope')] - }, - setCustomParameters: { - name: 'setCustomParameters', - args: [fireauth.args.object('customOAuthParameters')] - } - }); -fireauth.exportlib.exportFunction( - fireauth.GoogleAuthProvider, 'credential', - fireauth.GoogleAuthProvider.credential, [ - fireauth.args.or(fireauth.args.string(), - fireauth.args.or(fireauth.args.object(), fireauth.args.null()), - 'idToken'), - fireauth.args.or(fireauth.args.string(), fireauth.args.null(), - 'accessToken', true) - ]); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.TwitterAuthProvider.prototype, { - setCustomParameters: { - name: 'setCustomParameters', - args: [fireauth.args.object('customOAuthParameters')] - } - }); -fireauth.exportlib.exportFunction( - fireauth.TwitterAuthProvider, 'credential', - fireauth.TwitterAuthProvider.credential, [ - fireauth.args.or(fireauth.args.string(), fireauth.args.object(), - 'token'), - fireauth.args.string('secret', true) - ]); -fireauth.exportlib.exportPrototypeMethods( - fireauth.OAuthProvider.prototype, { - addScope: { - name: 'addScope', - args: [fireauth.args.string('scope')] - }, - credential: { - name: 'credential', - args: [ - fireauth.args.or( - fireauth.args.string(), - fireauth.args.or(fireauth.args.object(), fireauth.args.null()), - 'optionsOrIdToken'), - fireauth.args.or(fireauth.args.string(), fireauth.args.null(), - 'accessToken', true) - ] - }, - setCustomParameters: { - name: 'setCustomParameters', - args: [fireauth.args.object('customOAuthParameters')] - } - }); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.OAuthCredential.prototype, { - toPlainObject: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - } - }); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.SAMLAuthCredential.prototype, { - toPlainObject: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - } - }); - -fireauth.exportlib.exportFunction( - fireauth.PhoneAuthProvider, 'credential', - fireauth.PhoneAuthProvider.credential, [ - fireauth.args.string('verificationId'), - fireauth.args.string('verificationCode') - ]); -fireauth.exportlib.exportPrototypeMethods( - fireauth.PhoneAuthProvider.prototype, { - verifyPhoneNumber: { - name: 'verifyPhoneNumber', - args: [ - fireauth.args.or( - fireauth.args.string(), - fireauth.args.phoneInfoOptions(), - 'phoneInfoOptions'), - fireauth.args.applicationVerifier() - ] - } - }); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.PhoneAuthCredential.prototype, { - toPlainObject: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - } - }); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.AuthError.prototype, { - toJSON: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - } - }); -fireauth.exportlib.exportPrototypeMethods( - fireauth.AuthErrorWithCredential.prototype, { - toJSON: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - } - }); -fireauth.exportlib.exportPrototypeMethods( - fireauth.InvalidOriginError.prototype, { - toJSON: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - } - }); -fireauth.exportlib.exportPrototypeMethods( - fireauth.MultiFactorError.prototype, { - toJSON: { - name: 'toJSON', - // This shouldn't take an argument but a blank string is being passed - // on JSON.stringify and causing this to fail with an argument error. - // So allow an optional string. - args: [fireauth.args.string(null, true)] - } - }); -fireauth.exportlib.exportPrototypeMethods( - fireauth.MultiFactorResolver.prototype, { - resolveSignIn: { - name: 'resolveSignIn', - args: [fireauth.args.multiFactorAssertion()] - } - }); -fireauth.exportlib.exportPrototypeMethods( - fireauth.MultiFactorUser.prototype, { - getSession: { - name: 'getSession', - args: [] - }, - enroll: { - name: 'enroll', - args: [ - fireauth.args.multiFactorAssertion(), - fireauth.args.string('displayName', true) - ] - }, - unenroll: { - name: 'unenroll', - args: [ - fireauth.args.or( - fireauth.args.multiFactorInfo(), - fireauth.args.string(), - 'multiFactorInfoIdentifier') - ] - } - }); - -fireauth.exportlib.exportPrototypeMethods( - fireauth.RecaptchaVerifier.prototype, { - clear: { - name: 'clear', - args: [] - }, - render: { - name: 'render', - args: [] - }, - verify: { - name: 'verify', - args: [] - } - }); - -fireauth.exportlib.exportFunction( - fireauth.ActionCodeURL, 'parseLink', - fireauth.ActionCodeURL.parseLink, [fireauth.args.string('link')]); - -fireauth.exportlib.exportFunction( - fireauth.PhoneMultiFactorGenerator, 'assertion', - fireauth.PhoneMultiFactorGenerator.assertion, - [fireauth.args.authCredential(fireauth.idp.ProviderId.PHONE)]); - - -(function() { - if (typeof firebase === 'undefined' || !firebase.INTERNAL || - !firebase.INTERNAL.registerComponent) { - throw new Error('Cannot find the firebase namespace; be sure to include ' + - 'firebase-app.js before this library.'); - } else { - var namespace = { - // Exports firebase.auth.ActionCodeInfo.Operation. - 'ActionCodeInfo': { - 'Operation': { - 'EMAIL_SIGNIN': fireauth.ActionCodeInfo.Operation.EMAIL_SIGNIN, - 'PASSWORD_RESET': fireauth.ActionCodeInfo.Operation.PASSWORD_RESET, - 'RECOVER_EMAIL': fireauth.ActionCodeInfo.Operation.RECOVER_EMAIL, - 'REVERT_SECOND_FACTOR_ADDITION': - fireauth.ActionCodeInfo.Operation.REVERT_SECOND_FACTOR_ADDITION, - 'VERIFY_AND_CHANGE_EMAIL': - fireauth.ActionCodeInfo.Operation.VERIFY_AND_CHANGE_EMAIL, - 'VERIFY_EMAIL': fireauth.ActionCodeInfo.Operation.VERIFY_EMAIL - } - }, - 'Auth': fireauth.Auth, - 'AuthCredential': fireauth.AuthCredential, - 'Error': fireauth.AuthError - }; - fireauth.exportlib.exportFunction(namespace, - 'EmailAuthProvider', fireauth.EmailAuthProvider, []); - fireauth.exportlib.exportFunction(namespace, - 'FacebookAuthProvider', fireauth.FacebookAuthProvider, []); - fireauth.exportlib.exportFunction(namespace, - 'GithubAuthProvider', fireauth.GithubAuthProvider, []); - fireauth.exportlib.exportFunction(namespace, - 'GoogleAuthProvider', fireauth.GoogleAuthProvider, []); - fireauth.exportlib.exportFunction(namespace, - 'TwitterAuthProvider', fireauth.TwitterAuthProvider, []); - fireauth.exportlib.exportFunction(namespace, - 'OAuthProvider', fireauth.OAuthProvider, [ - fireauth.args.string('providerId') - ]); - fireauth.exportlib.exportFunction(namespace, - 'SAMLAuthProvider', fireauth.SAMLAuthProvider, [ - fireauth.args.string('providerId') - ]); - fireauth.exportlib.exportFunction(namespace, - 'PhoneAuthProvider', fireauth.PhoneAuthProvider, [ - fireauth.args.firebaseAuth(true) - ]); - fireauth.exportlib.exportFunction(namespace, - 'RecaptchaVerifier', fireauth.RecaptchaVerifier, [ - fireauth.args.or( - fireauth.args.string(), - fireauth.args.element(), - 'recaptchaContainer'), - fireauth.args.object('recaptchaParameters', true), - fireauth.args.firebaseApp(true) - ]); - fireauth.exportlib.exportFunction(namespace, - 'ActionCodeURL', fireauth.ActionCodeURL, []); - fireauth.exportlib.exportFunction(namespace, - 'PhoneMultiFactorGenerator', fireauth.PhoneMultiFactorGenerator, []); - - // Create auth components to register with firebase. - // Provides Auth public APIs. - const authComponent = { - 'name': fireauth.exportlib.AUTH_TYPE, - 'instanceFactory': function(container) { - var app = container['getProvider']('app')['getImmediate'](); - return new fireauth.Auth(app); - }, - 'multipleInstances': false, - 'serviceProps': namespace, - 'instantiationMode': 'LAZY', - 'type': 'PUBLIC', - /** - * Initialize auth-internal after auth is initialized to make auth available to other firebase products. - */ - 'onInstanceCreated': function (container, _instanceIdentifier, _instance) { - const authInternalProvider = container['getProvider']( - 'auth-internal' - ); - authInternalProvider['initialize'](); - } - }; - - // Provides Auth internal APIs. - const authInteropComponent = { - 'name': 'auth-internal', - 'instanceFactory': function(container) { - var auth = container['getProvider'](fireauth.exportlib.AUTH_TYPE)['getImmediate'](); - return { - 'getUid': goog.bind(auth.getUid, auth), - 'getToken': goog.bind(auth.getIdTokenInternal, auth), - 'addAuthTokenListener': - goog.bind(auth.addAuthTokenListenerInternal, auth), - 'removeAuthTokenListener': - goog.bind(auth.removeAuthTokenListenerInternal, auth) - }; - }, - 'multipleInstances': false, - 'instantiationMode': 'LAZY', - 'type': 'PRIVATE' - }; - - firebase.INTERNAL.registerComponent(authComponent); - firebase.INTERNAL.registerComponent(authInteropComponent); - firebase.registerVersion('@firebase/auth', AUTH_NPM_PACKAGE_VERSION); - - // Expose User as firebase.User. - firebase.INTERNAL.extendNamespace({ - 'User': fireauth.AuthUser - }); - } -})(); \ No newline at end of file diff --git a/packages/auth/src/exports_lib.js b/packages/auth/src/exports_lib.js deleted file mode 100644 index 9f8f6f67112..00000000000 --- a/packages/auth/src/exports_lib.js +++ /dev/null @@ -1,224 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Provides utilities for exporting public APIs, with error - * checking. - */ - -goog.provide('fireauth.exportlib'); -goog.provide('fireauth.exportlib.ExportedMethod'); - -goog.require('fireauth.args'); - - -/** - * Type constant for Firebase Auth. - * @const {string} - */ -fireauth.exportlib.AUTH_TYPE = 'auth'; - - -/** - * Represents an exported method, with the exported name of the method and the - * expected arguments to that method. - * @typedef {{ - * name: string, - * args: (Array|null|undefined) - * }} - */ -fireauth.exportlib.ExportedMethod; - - -/** - * Represents an exported property, with the exported name of the property and - * the expected argument to the setter of this property. - * @typedef {{ - * name: string, - * arg: !fireauth.args.Argument - * }} - */ -fireauth.exportlib.ExportedProperty; - - -/** - * Exports prototype methods of an object. - * @param {!Object} protObj The prototype of an object. - * @param {!Object} fnMap The map of - * prototype functions to their export name and expected arguments. - */ -fireauth.exportlib.exportPrototypeMethods = function(protObj, fnMap) { - // This method exports methods by aliasing the unobfuscated function name - // (specified as a string in the "name" field of ExportedMethod) to the - // obfuscated function name (specified as a key of the fnMap object). - // - // To give a concrete example, let's say that we have this method: - // fireauth.Auth.prototype.fetchProvidersForEmail = function() { ... }; - // - // In the exports file, we export as follows: - // fireauth.exportlib.exportPrototypeMethods(fireauth.Auth.prototype, { - // fetchProvidersForEmail: {name: 'fetchProvidersForEmail', args: ...} - // }); - // - // When the compiler obfuscates the code, the code above will become something - // like this: - // fireauth.Auth.prototype.qZ = function() { ... }; - // fireauth.exportlib.exportPrototypeMethods(fireauth.Auth.prototype, { - // qZ: {name: 'fetchProvidersForEmail', args: ...} - // }); - // - // (Of course, fireauth.Auth and fireauth.exportlib.exportPrototypeMethods - // would also be obfuscated). Note that the key in fnMap is obfuscated but the - // "name" field in the ExportedMethod is not. Now, exportPrototypeMethods can - // export fetchProvidersForEmail by reading the key ("qZ") and the "name" - // field ("fetchProvidersForEmail") and essentially executing this: - // fireauth.Auth.prototype['fetchProvidersForEmail'] = - // fireauth.Auth.prototype['qZ']; - for (var obfuscatedFnName in fnMap) { - var unobfuscatedFnName = fnMap[obfuscatedFnName].name; - protObj[unobfuscatedFnName] = - fireauth.exportlib.wrapMethodWithArgumentVerifier_( - unobfuscatedFnName, protObj[obfuscatedFnName], - fnMap[obfuscatedFnName].args); - } -}; - - -/** - * Exports properties of an object. See the docs for exportPrototypeMethods for - * more information about how this works. - * @param {!Object} protObj The prototype of an object. - * @param {!Object} propMap The - * map of properties to their export names. - */ -fireauth.exportlib.exportPrototypeProperties = function(protObj, propMap) { - for (var obfuscatedPropName in propMap) { - var unobfuscatedPropName = propMap[obfuscatedPropName].name; - // Don't alias a property to itself. - // Downside is that argument validation will not be possible. For now, to - // get around it, ensure unobfuscated property names are different - // than the corresponding obfuscated property names. - if (unobfuscatedPropName === obfuscatedPropName) { - continue; - } - /** - * @this {!Object} - * @param {string} obfuscatedPropName The obfuscated property name. - * @return {*} The value of the property. - */ - var getter = function(obfuscatedPropName) { - return this[obfuscatedPropName]; - }; - /** - * @this {!Object} - * @param {string} unobfuscatedPropName The unobfuscated property name. - * @param {string} obfuscatedPropName The obfuscated property name. - * @param {!fireauth.args.Argument} expectedArg The expected argument to the - * setter of this property. - * @param {*} value The new value of the property. - */ - var setter = function(unobfuscatedPropName, obfuscatedPropName, - expectedArg, value) { - // Validate the argument before setting it. - fireauth.args.validate( - unobfuscatedPropName, [expectedArg], [value], true); - this[obfuscatedPropName] = value; - }; - // Get the expected argument. - var expectedArg = propMap[obfuscatedPropName].arg; - Object.defineProperty(protObj, unobfuscatedPropName, { - /** - * @this {!Object} - * @return {*} The value of the property. - */ - get: goog.partial(getter, obfuscatedPropName), - /** - * @this {!Object} - * @param {*} value The new value of the property. - */ - set: goog.partial(setter, unobfuscatedPropName, obfuscatedPropName, - expectedArg), - enumerable: true - }); - } -}; - - -/** - * Export a static method as a public API. - * @param {!Object} parentObj The parent object to patch. - * @param {string} name The public name of the method. - * @param {!Function} func The method. - * @param {?Array=} opt_expectedArgs The expected - * arguments to the method. - */ -fireauth.exportlib.exportFunction = function(parentObj, name, func, - opt_expectedArgs) { - parentObj[name] = fireauth.exportlib.wrapMethodWithArgumentVerifier_( - name, func, opt_expectedArgs); -}; - - -/** - * Wraps a method with a function that first verifies the arguments to the - * method and then calls the original method. - * @param {string} methodName The name of the method, which will be displayed - * on the error message if the arguments are not valid. - * @param {!Function} method The method to be wrapped. - * @param {?Array=} opt_expectedArgs The expected - * arguments. - * @return {!Function} The wrapped method. - * @private - */ -fireauth.exportlib.wrapMethodWithArgumentVerifier_ = function(methodName, - method, opt_expectedArgs) { - if (!opt_expectedArgs) { - return method; - } - var shortName = fireauth.exportlib.extractMethodNameFromFullPath_(methodName); - var wrapper = function() { - var argumentsAsArray = Array.prototype.slice.call(arguments); - fireauth.args.validate(shortName, - /** @type {!Array} */ (opt_expectedArgs), - argumentsAsArray); - return method.apply(this, argumentsAsArray); - }; - // Reattach all static stuff to wrapper. - for (var key in method) { - wrapper[key] = method[key]; - } - // Reattach all prototype stuff to wrapper. - for (var key in method.prototype) { - wrapper.prototype[key] = method.prototype[key]; - } - // Return wrapper with all of method's static and prototype methods and - // properties. - return wrapper; -}; - - -/** - * From a full path to a method (e.g. "fireauth.GoogleAuthProvider.credential"), - * get just the method name ("credential"). - * @param {string} path The full path. - * @return {string} The method name. - * @private - */ -fireauth.exportlib.extractMethodNameFromFullPath_ = function(path) { - var parts = path.split('.'); - return parts[parts.length - 1]; -}; diff --git a/packages/auth/src/exports_unreleased.js b/packages/auth/src/exports_unreleased.js deleted file mode 100644 index 636ace7bda0..00000000000 --- a/packages/auth/src/exports_unreleased.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Exports symbols for unreleased features. This file is not - * included in the Firebase JS build. - * - * When a feature is to be released in the next Firebase JS release, move the - * export from this file to exports_auth.js. - */ - -goog.provide('fireauth.exportsUnreleased'); diff --git a/packages/auth/src/externs.js b/packages/auth/src/externs.js deleted file mode 100644 index 502df137d3a..00000000000 --- a/packages/auth/src/externs.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase Auth-specific externs. - */ - - -/** - * A verifier that asserts that the user calling an API is a real user. - * @interface - */ -firebase.auth.ApplicationVerifier = function() {}; - - -/** - * The type of the ApplicationVerifier assertion, e.g. "recaptcha". - * @type {string} - */ -firebase.auth.ApplicationVerifier.prototype.type; - - -/** - * Returns a promise for the assertion to verify the app identity, e.g. the - * g-recaptcha-response in reCAPTCHA. - * @return {!firebase.Promise} - */ -firebase.auth.ApplicationVerifier.prototype.verify = function() {}; diff --git a/packages/auth/src/idp.js b/packages/auth/src/idp.js deleted file mode 100644 index 9d93f40941c..00000000000 --- a/packages/auth/src/idp.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the IdP provider IDs and related settings. - */ - -goog.provide('fireauth.idp'); -goog.provide('fireauth.idp.IdpSettings'); -goog.provide('fireauth.idp.ProviderId'); -goog.provide('fireauth.idp.Settings'); -goog.provide('fireauth.idp.SignInMethod'); - -goog.require('fireauth.constants'); - - -/** - * Enums for supported provider IDs. These provider IDs correspond to the - * sign_in_provider in the Firebase ID token and do not correspond to the - * supported client exposed firebase.auth.AuthProviders. - * @enum {string} - */ -fireauth.idp.ProviderId = { - ANONYMOUS: 'anonymous', - CUSTOM: 'custom', - FACEBOOK: 'facebook.com', - FIREBASE: 'firebase', - GITHUB: 'github.com', - GOOGLE: 'google.com', - PASSWORD: 'password', - PHONE: 'phone', - TWITTER: 'twitter.com' -}; - - -/** - * Enums for supported sign in methods. - * @enum {string} - */ -fireauth.idp.SignInMethod = { - EMAIL_LINK: 'emailLink', - EMAIL_PASSWORD: 'password', - FACEBOOK: 'facebook.com', - GITHUB: 'github.com', - GOOGLE: 'google.com', - PHONE: 'phone', - TWITTER: 'twitter.com' -}; - - -/** - * The settings of an identity provider. The fields are: - *
    - *
  • languageParam: defines the custom OAuth language parameter. - *
  • popupWidth: defines the popup recommended width. - *
  • popupHeight: defines the popup recommended height. - *
  • providerId: defines the provider ID. - *
  • reservedOAuthParameters: defines the list of reserved OAuth parameters. - *
- * @typedef {{ - * languageParam: (?string|undefined), - * popupWidth: (?number|undefined), - * popupHeight: (?number|undefined), - * providerId: !fireauth.idp.ProviderId, - * reservedOAuthParameters: !Array - * }} - */ -fireauth.idp.IdpSettings; - - -/** - * The list of reserved OAuth 1.0 parameters. - * @const {!Array} - */ -fireauth.idp.RESERVED_OAUTH1_PARAMS = - ['oauth_consumer_key', 'oauth_nonce', 'oauth_signature', - 'oauth_signature_method', 'oauth_timestamp', 'oauth_token', - 'oauth_version']; - - -/** - * The list of reserved OAuth 2.0 parameters. - * @const {!Array} - */ -fireauth.idp.RESERVED_OAUTH2_PARAMS = - ['client_id', 'response_type', 'scope', 'redirect_uri', 'state']; - - -/** - * The recommendations for the different IdP display settings. - * @enum {!fireauth.idp.IdpSettings} - */ -fireauth.idp.Settings = { - FACEBOOK: { - languageParam: 'locale', - popupWidth: 700, - popupHeight: 600, - providerId: fireauth.idp.ProviderId.FACEBOOK, - reservedOAuthParameters: fireauth.idp.RESERVED_OAUTH2_PARAMS, - }, - GITHUB: { - languageParam: null, - popupWidth: 500, - popupHeight: 750, - providerId: fireauth.idp.ProviderId.GITHUB, - reservedOAuthParameters: fireauth.idp.RESERVED_OAUTH2_PARAMS, - }, - GOOGLE: { - languageParam: 'hl', - popupWidth: 515, - popupHeight: 680, - providerId: fireauth.idp.ProviderId.GOOGLE, - reservedOAuthParameters: fireauth.idp.RESERVED_OAUTH2_PARAMS, - }, - TWITTER: { - languageParam: 'lang', - popupWidth: 485, - popupHeight: 705, - providerId: fireauth.idp.ProviderId.TWITTER, - reservedOAuthParameters: fireauth.idp.RESERVED_OAUTH1_PARAMS, - }, - APPLE: { - languageParam: 'locale', - popupWidth: 640, - popupHeight: 600, - providerId: 'apple.com', - reservedOAuthParameters: [], - }, -}; - - -/** - * @param {!fireauth.idp.ProviderId} providerId The requested provider ID. - * @return {?fireauth.idp.Settings} The settings for the requested provider ID. - */ -fireauth.idp.getIdpSettings = function(providerId) { - for (var key in fireauth.idp.Settings) { - if (fireauth.idp.Settings[key].providerId == providerId) { - return fireauth.idp.Settings[key]; - } - } - return null; -}; - - -/** - * @param {!fireauth.idp.ProviderId} providerId The requested provider ID. - * @return {!Array} The list of reserved OAuth parameters. - */ -fireauth.idp.getReservedOAuthParams = function(providerId) { - var settings = fireauth.idp.getIdpSettings(providerId); - return (settings && settings.reservedOAuthParameters) || []; -}; - - -/** - * @param {?string|undefined} identifier The provider identifier. - * @return {boolean} Whether the identifier provided is a SAML provider ID. - */ -fireauth.idp.isSaml = function(identifier) { - return typeof identifier === 'string' && - identifier.indexOf(fireauth.constants.SAML_PREFIX) == 0; -}; diff --git a/packages/auth/src/idtoken.js b/packages/auth/src/idtoken.js deleted file mode 100644 index 4a87364b6a6..00000000000 --- a/packages/auth/src/idtoken.js +++ /dev/null @@ -1,254 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Utility functions to handle Firebase Auth ID tokens. - */ - -goog.provide('fireauth.IdToken'); - -goog.require('goog.crypt'); -goog.require('goog.crypt.base64'); - - -/** - * Parses the token string into a {@code Token} object. - * @param {?string} tokenString The JWT token string. - * @constructor - */ -fireauth.IdToken = function(tokenString) { - const token = fireauth.IdToken.parseIdTokenClaims(tokenString); - if (!(token && token['sub'] && token['iss'] && - token['aud'] && token['exp'])) { - throw new Error('Invalid JWT'); - } - /** @const @private {string} The plain JWT string. */ - this.jwt_ = /** @type {string} */ (tokenString); - /** @const @private {string} The issuer of the token. */ - this.iss_ = token['iss']; - /** @const @private {string} The audience of the token. */ - this.aud_ = token['aud']; - /** @const @private {number} The expire time in seconds of the token. */ - this.exp_ = token['exp']; - /** @const @private {string} The local user ID of the token. */ - this.localId_ = token['sub']; - const now = Date.now() / 1000; - /** @const @private {number} The issue time in seconds of the token. */ - this.iat_ = token['iat'] || (now > this.exp_ ? this.exp_ : now); - /** @const @private {?string} The email address of the token. */ - this.email_ = token['email'] || null; - /** @const @private {boolean} Whether the user is verified. */ - this.verified_ = !!token['verified']; - /** @const @private {?string} The provider ID of the token. */ - this.providerId_ = token['provider_id'] || - (token['firebase'] && token['firebase']['sign_in_provider']) || - null; - /** @const @private {?string} The tenant ID of the token. */ - this.tenantId_ = (token['firebase'] && token['firebase']['tenant']) || null; - /** @const @private {boolean} Whether the user is anonymous. */ - this.anonymous_ = !!token['is_anonymous'] || this.providerId_ == 'anonymous'; - /** @const @private {?string} The federated ID of the token. */ - this.federatedId_ = token['federated_id'] || null; - /** @const @private {?string} The display name of the token. */ - this.displayName_ = token['display_name'] || null; - /** @const @private {?string} The photo URL of the token. */ - this.photoURL_ = token['photo_url'] || null; - /** - * @const @private {?string} The phone number of the user identified by the - * token. - */ - this.phoneNumber_ = token['phone_number'] || null; -}; - - -/** - * @typedef {{ - * identities: (?Object|undefined), - * sign_in_provider: (?string|undefined), - * tenant: (string|undefined) - * }} - */ -fireauth.IdToken.Firebase; - - -/** - * @typedef {{ - * iss: string, - * aud: string, - * exp: number, - * sub: string, - * iat: (?number|undefined), - * email: (?string|undefined), - * verified: (?boolean|undefined), - * provider_id: (?string|undefined), - * is_anonymous: (?boolean|undefined), - * federated_id: (?string|undefined), - * display_name: (?string|undefined), - * photo_url: (?string|undefined), - * phone_number: (?string|undefined), - * firebase: (?fireauth.IdToken.Firebase|undefined) - * }} - */ -fireauth.IdToken.JsonToken; - - -/** @return {?string} The email address of the account. */ -fireauth.IdToken.prototype.getEmail = function() { - return this.email_; -}; - - -/** - * @deprecated Use client side clock to calculate when the token expires. - * @return {number} The expire time in seconds. - */ -fireauth.IdToken.prototype.getExp = function() { - return this.exp_; -}; - - -/** - * @return {number} The difference in seconds between when the token was - * issued and when it expires. - */ -fireauth.IdToken.prototype.getExpiresIn = function() { - return this.exp_ - this.iat_; -}; - - -/** @return {?string} The ID of the identity provider. */ -fireauth.IdToken.prototype.getProviderId = function() { - return this.providerId_; -}; - - -/** @return {?string} The tenant ID. */ -fireauth.IdToken.prototype.getTenantId = function() { - return this.tenantId_; -}; - - -/** @return {?string} The display name of the account. */ -fireauth.IdToken.prototype.getDisplayName = function() { - return this.displayName_; -}; - - -/** @return {?string} The photo URL of the account. */ -fireauth.IdToken.prototype.getPhotoUrl = function() { - return this.photoURL_; -}; - - -/** @return {string} The user ID of the account. */ -fireauth.IdToken.prototype.getLocalId = function() { - return this.localId_; -}; - - -/** @return {?string} The federated ID of the account. */ -fireauth.IdToken.prototype.getFederatedId = function() { - return this.federatedId_; -}; - - -/** @return {boolean} Whether the user is anonymous. */ -fireauth.IdToken.prototype.isAnonymous = function() { - return this.anonymous_; -}; - - -/** @return {boolean} Whether the user email is verified. */ -fireauth.IdToken.prototype.isVerified = function() { - return this.verified_; -}; - - -/** - * @deprecated Use client side clock to calculate when the token expires. - * @return {boolean} Whether token is expired. - */ -fireauth.IdToken.prototype.isExpired = function() { - const now = Math.floor(Date.now() / 1000); - // It is expired if token expiration time is less than current time. - return this.getExp() <= now; -}; - - -/** @return {string} The issuer of the token. */ -fireauth.IdToken.prototype.getIssuer = function() { - return this.iss_; -}; - - -/** @return {?string} The phone number of the account. */ -fireauth.IdToken.prototype.getPhoneNumber = function() { - return this.phoneNumber_; -}; - - -/** - * @return {string} The JWT string. - * @override - */ -fireauth.IdToken.prototype.toString = function() { - return this.jwt_; -}; - - -/** - * Parses the JWT token and extracts the information part without verifying the - * token signature. - * @param {string} tokenString The JWT token. - * @return {?fireauth.IdToken} The decoded token. - */ -fireauth.IdToken.parse = function(tokenString) { - try { - return new fireauth.IdToken(tokenString); - } catch (e) { - return null; - } -}; - -/** - * Converts the information part of JWT token to plain object format. - * @param {?string} tokenString The JWT token. - * @return {?Object} - */ -fireauth.IdToken.parseIdTokenClaims = function(tokenString) { - if (!tokenString) { - return null; - } - // Token format is .. - const fields = tokenString.split('.'); - if (fields.length != 3) { - return null; - } - let jsonInfo = fields[1]; - // Google base64 library does not handle padding. - const padLen = (4 - jsonInfo.length % 4) % 4; - for (let i = 0; i < padLen; i++) { - jsonInfo += '.'; - } - try { - const decodedClaims = goog.crypt.utf8ByteArrayToString( - goog.crypt.base64.decodeStringToByteArray(jsonInfo)); - const token = JSON.parse(decodedClaims); - return /** @type {?Object} */ (token); - } catch (e) {} - return null; -}; diff --git a/packages/auth/src/idtokenresult.js b/packages/auth/src/idtokenresult.js deleted file mode 100644 index 5593a52a6db..00000000000 --- a/packages/auth/src/idtokenresult.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the firebase.auth.IdTokenResult class that is obtained - * from getIdTokenResult. It contains the ID token JWT string and other helper - * properties for getting different data associated with the token as well as - * all the decoded payload claims. - */ - -goog.provide('fireauth.IdTokenResult'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.IdToken'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.object'); -goog.require('fireauth.util'); - - - -/** - * This is the ID token result object obtained from getIdTokenResult. It - * contains the ID token JWT string and other helper properties for getting - * different data associated with the token as well as all the decoded payload - * claims. - * @param {string} tokenString The JWT token. - * @constructor - */ -fireauth.IdTokenResult = function(tokenString) { - var idToken = fireauth.IdToken.parseIdTokenClaims(tokenString); - if (!idToken || !idToken['exp'] || !idToken['auth_time'] || !idToken['iat']) { - throw new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'An internal error occurred. The token obtained by Firebase appears ' + - 'to be malformed. Please retry the operation.'); - } - fireauth.object.setReadonlyProperties(this, { - 'token': tokenString, - 'expirationTime': fireauth.util.utcTimestampToDateString( - idToken['exp'] * 1000), - 'authTime': fireauth.util.utcTimestampToDateString( - idToken['auth_time'] * 1000), - 'issuedAtTime': fireauth.util.utcTimestampToDateString( - idToken['iat'] * 1000), - 'signInProvider': (idToken['firebase'] && - idToken['firebase']['sign_in_provider']) ? - idToken['firebase']['sign_in_provider'] : null, - 'signInSecondFactor': (idToken['firebase'] && - idToken['firebase']['sign_in_second_factor']) ? - idToken['firebase']['sign_in_second_factor'] : null, - 'claims': idToken - }); -}; diff --git a/packages/auth/src/iframeclient/ifchandler.js b/packages/auth/src/iframeclient/ifchandler.js deleted file mode 100644 index 98459ea844f..00000000000 --- a/packages/auth/src/iframeclient/ifchandler.js +++ /dev/null @@ -1,1079 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines fireauth.iframeclient.IfcHandler used to communicate - * with the serverless widget. - */ - -goog.provide('fireauth.iframeclient.IfcHandler'); -goog.provide('fireauth.iframeclient.IframeUrlBuilder'); -goog.provide('fireauth.iframeclient.OAuthUrlBuilder'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.AuthProvider'); -goog.require('fireauth.InvalidOriginError'); -goog.require('fireauth.OAuthSignInHandler'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.iframeclient.IframeWrapper'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Timer'); -goog.require('goog.Uri'); -goog.require('goog.array'); -goog.require('goog.object'); - - -/** - * The OAuth handler and iframe prototcol. - * @const {string} - * @suppress {const|duplicate} - */ -fireauth.iframeclient.SCHEME = 'https'; - - - -/** - * The OAuth handler and iframe port number. - * @const {?number} - * @suppress {const|duplicate} - */ -fireauth.iframeclient.PORT_NUMBER = null; - - - -/** - * The iframe URL builder used to build the iframe widget URL. - * @param {string} authDomain The application authDomain. - * @param {string} apiKey The API key. - * @param {string} appName The App name. - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @constructor - */ -fireauth.iframeclient.IframeUrlBuilder = function(authDomain, apiKey, appName, emulatorConfig) { - /** @private {string} The application authDomain. */ - this.authDomain_ = authDomain; - /** @private {string} The API key. */ - this.apiKey_ = apiKey; - /** @private {string} The App name. */ - this.appName_ = appName; - /** - * @private @const {?fireauth.constants.EmulatorSettings|undefined} - * The emulator configuration. - */ - this.emulatorConfig_ = emulatorConfig; - /** @private {?string|undefined} The client version. */ - this.v_ = null; - let uri; - if (this.emulatorConfig_) { - const emulatorUri = goog.Uri.parse(this.emulatorConfig_.url); - uri = goog.Uri.create( - emulatorUri.getScheme(), - null, - emulatorUri.getDomain(), - emulatorUri.getPort(), - '/emulator/auth/iframe', - null, - null); - } else { - uri = goog.Uri.create( - fireauth.iframeclient.SCHEME, - null, - this.authDomain_, - fireauth.iframeclient.PORT_NUMBER, - '/__/auth/iframe', - null, - null); - } - /** - * @private @const {!goog.Uri} The URI object used to build the iframe URL. - */ - this.uri_ = uri; - this.uri_.setParameterValue('apiKey', this.apiKey_); - this.uri_.setParameterValue('appName', this.appName_); - /** @private {?string|undefined} The endpoint ID. */ - this.endpointId_ = null; - /** @private {!Array} The list of framework IDs. */ - this.frameworks_ = []; -}; - - -/** - * Sets the client version. - * @param {?string|undefined} v The client version. - * @return {!fireauth.iframeclient.IframeUrlBuilder} The current iframe URL - * builder instance. - */ -fireauth.iframeclient.IframeUrlBuilder.prototype.setVersion = function(v) { - this.v_ = v; - return this; -}; - - -/** - * Sets the endpoint ID. - * @param {?string|undefined} eid The endpoint ID (staging, test Gaia, etc). - * @return {!fireauth.iframeclient.IframeUrlBuilder} The current iframe URL - * builder instance. - */ -fireauth.iframeclient.IframeUrlBuilder.prototype.setEndpointId = function(eid) { - this.endpointId_ = eid; - return this; -}; - - -/** - * Sets the list of frameworks to pass to the iframe. - * @param {?Array|undefined} frameworks The list of frameworks to log. - * @return {!fireauth.iframeclient.IframeUrlBuilder} The current iframe URL - * builder instance. - */ -fireauth.iframeclient.IframeUrlBuilder.prototype.setFrameworks = - function(frameworks) { - this.frameworks_ = goog.array.clone(frameworks || []); - return this; -}; - - -/** - * Modifes the URI with the relevant Auth provider parameters. - * @return {string} The constructed OAuth URL string. - * @override - */ -fireauth.iframeclient.IframeUrlBuilder.prototype.toString = function() { - // Pass the client version if available. - if (this.v_) { - this.uri_.setParameterValue('v', this.v_); - } else { - this.uri_.removeParameter('v'); - } - // Pass the endpoint ID if available. - if (this.endpointId_) { - this.uri_.setParameterValue('eid', this.endpointId_); - } else { - this.uri_.removeParameter('eid'); - } - // Pass the list of frameworks if available. - if (this.frameworks_.length) { - this.uri_.setParameterValue('fw', this.frameworks_.join(',')); - } else { - this.uri_.removeParameter('fw'); - } - return this.uri_.toString(); -}; - - - -/** - * The OAuth URL builder used to build the OAuth handler widget URL. - * @param {string} authDomain The application authDomain. - * @param {string} apiKey The API key. - * @param {string} appName The App name. - * @param {string} authType The Auth operation type. - * @param {!fireauth.AuthProvider} provider The Auth provider that the OAuth - * handler request is built to sign in to. - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @constructor - */ -fireauth.iframeclient.OAuthUrlBuilder = - function(authDomain, apiKey, appName, authType, provider, emulatorConfig) { - /** @private {string} The application authDomain. */ - this.authDomain_ = authDomain; - /** @private {string} The API key. */ - this.apiKey_ = apiKey; - /** @private {string} The App name. */ - this.appName_ = appName; - /** @private {string} The Auth operation type. */ - this.authType_ = authType; - /** - * @private @const {?fireauth.constants.EmulatorSettings|undefined} - * The emulator configuration. - */ - this.emulatorConfig_ = emulatorConfig; - /** - * @private {?string|undefined} The redirect URL used in redirect operations. - */ - this.redirectUrl_ = null; - /** @private {?string|undefined} The event ID. */ - this.eventId_ = null; - /** @private {?string|undefined} The client version. */ - this.v_ = null; - /** - * @private {!fireauth.AuthProvider} The Firebase Auth provider that the OAuth - * handler request is built to sign in to. - */ - this.provider_ = provider; - /** @private {?string|undefined} The endpoint ID. */ - this.endpointId_ = null; - /** @private {?string|undefined} The tenant ID. */ - this.tenantId_ = null; -}; - - -/** - * Sets the redirect URL. - * @param {?string|undefined} redirectUrl The redirect URL used in redirect - * operations. - * @return {!fireauth.iframeclient.OAuthUrlBuilder} The current OAuth URL - * builder instance. - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.setRedirectUrl = - function(redirectUrl) { - this.redirectUrl_ = redirectUrl; - return this; -}; - - -/** - * Sets the event ID. - * @param {?string|undefined} eventId The event ID. - * @return {!fireauth.iframeclient.OAuthUrlBuilder} The current OAuth URL - * builder instance. - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.setEventId = function(eventId) { - this.eventId_ = eventId; - return this; -}; - - -/** - * Sets the tenant ID. - * @param {?string|undefined} tenantId The event ID. - * @return {!fireauth.iframeclient.OAuthUrlBuilder} The current OAuth URL - * builder instance. - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.setTenantId = - function(tenantId) { - this.tenantId_ = tenantId; - return this; -}; - - -/** - * Sets the client version. - * @param {?string|undefined} v The client version. - * @return {!fireauth.iframeclient.OAuthUrlBuilder} The current OAuth URL - * builder instance. - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.setVersion = function(v) { - this.v_ = v; - return this; -}; - - -/** - * Sets the endpoint ID. - * @param {?string|undefined} eid The endpoint ID (staging, test Gaia, etc). - * @return {!fireauth.iframeclient.OAuthUrlBuilder} The current OAuth URL - * builder instance. - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.setEndpointId = function(eid) { - this.endpointId_ = eid; - return this; -}; - - -/** - * Sets any additional optional parameters. This will overwrite any previously - * set additional parameters. - * @param {?Object|undefined} additionalParams The optional - * additional parameters. - * @return {!fireauth.iframeclient.OAuthUrlBuilder} The current OAuth URL - * builder instance. - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.setAdditionalParameters = - function(additionalParams) { - this.additionalParams_ = goog.object.clone(additionalParams || null); - return this; -}; - - -/** - * Modifies the URI with the relevant Auth provider parameters. - * @return {string} The constructed OAuth URL string. - * @override - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.toString = function () { - var uri; - if (this.emulatorConfig_) { - const emulatorUri = goog.Uri.parse(this.emulatorConfig_.url); - uri = goog.Uri.create( - emulatorUri.getScheme(), - null, - emulatorUri.getDomain(), - emulatorUri.getPort(), - '/emulator/auth/handler', - null, - null); - } else { - uri = goog.Uri.create( - fireauth.iframeclient.SCHEME, - null, - this.authDomain_, - fireauth.iframeclient.PORT_NUMBER, - '/__/auth/handler', - null, - null); - } - uri.setParameterValue('apiKey', this.apiKey_); - uri.setParameterValue('appName', this.appName_); - uri.setParameterValue('authType', this.authType_); - - // Add custom parameters for OAuth1/OAuth2 providers. - if (this.provider_['isOAuthProvider']) { - // Set default language if available and no language already set. - /** @type {!fireauth.FederatedProvider} */ (this.provider_) - .setDefaultLanguage(this.getAuthLanguage_()); - uri.setParameterValue('providerId', this.provider_['providerId']); - var customParameters = /** @type {!fireauth.FederatedProvider} */ ( - this.provider_).getCustomParameters(); - if (!goog.object.isEmpty(customParameters)) { - uri.setParameterValue( - 'customParameters', - /** @type {string} */ (fireauth.util.stringifyJSON(customParameters)) - ); - } - } - - // Add scopes for OAuth2 providers. - if (typeof this.provider_.getScopes === 'function') { - var scopes = this.provider_.getScopes(); - if (scopes.length) { - uri.setParameterValue('scopes', scopes.join(',')); - } - } - - if (this.redirectUrl_) { - uri.setParameterValue('redirectUrl', this.redirectUrl_); - } else { - uri.removeParameter('redirectUrl'); - } - if (this.eventId_) { - uri.setParameterValue('eventId', this.eventId_); - } else { - uri.removeParameter('eventId'); - } - // Pass the client version if available. - if (this.v_) { - uri.setParameterValue('v', this.v_); - } else { - uri.removeParameter('v'); - } - if (this.additionalParams_) { - for (var key in this.additionalParams_) { - if (this.additionalParams_.hasOwnProperty(key) && - // Don't overwrite other existing parameters. - !uri.getParameterValue(key)) { - uri.setParameterValue(key, this.additionalParams_[key]); - } - } - } - // Pass the tenant ID if available. - if (this.tenantId_) { - uri.setParameterValue('tid', this.tenantId_); - } else { - uri.removeParameter('tid'); - } - // Pass the endpoint ID if available. - if (this.endpointId_) { - uri.setParameterValue('eid', this.endpointId_); - } else { - uri.removeParameter('eid'); - } - // Append any framework IDs to the handler URL to log in handler RPC requests. - var frameworks = this.getAuthFrameworks_(); - if (frameworks.length) { - uri.setParameterValue('fw', frameworks.join(',')); - } - return uri.toString(); -}; - - -/** - * Returns the current Auth instance's language code. - * @return {?string} The corresponding language code. - * @private - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.getAuthLanguage_ = function() { - try { - // Get the Auth instance for the current App identified by the App name. - // This could fail if, for example, the App instance was deleted. - return firebase['app'](this.appName_)['auth']().getLanguageCode(); - } catch (e) { - return null; - } -}; - - -/** - * Returns the list of Firebase frameworks used for logging purposes. - * @return {!Array} The list of corresponding Firebase frameworks. - * @private - */ -fireauth.iframeclient.OAuthUrlBuilder.prototype.getAuthFrameworks_ = - function() { - return fireauth.iframeclient.OAuthUrlBuilder.getAuthFrameworksForApp_( - this.appName_); -}; - - -/** - * Returns the list of Firebase frameworks used for logging purposes - * corresponding to the Firebase App name provided. - * @param {string} appName The Firebase App name. - * @return {!Array} The list of corresponding Firebase frameworks. - * @private - */ -fireauth.iframeclient.OAuthUrlBuilder.getAuthFrameworksForApp_ = - function(appName) { - try { - // Get the Auth instance's list of Firebase framework IDs for the current - // App identified by the App name. - // This could fail if, for example, the App instance was deleted. - return firebase['app'](appName)['auth']().getFramework(); - } catch (e) { - return []; - } -}; - - - -/** - * Initializes the ifcHandler which provides the mechanism to listen to Auth - * events on the hidden iframe. - * @param {string} authDomain The firebase authDomain used to determine the - * OAuth helper page domain. - * @param {string} apiKey The API key for sending backend Auth requests. - * @param {string} appName The App ID for the Auth instance that triggered this - * request. - * @param {?string=} opt_clientVersion The optional client version string. - * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @constructor - * @implements {fireauth.OAuthSignInHandler} - */ -fireauth.iframeclient.IfcHandler = function(authDomain, apiKey, appName, - opt_clientVersion, opt_endpointId, emulatorConfig) { - /** @private {string} The Auth domain. */ - this.authDomain_ = authDomain; - /** @private {string} The API key. */ - this.apiKey_ = apiKey; - /** @private {string} The App name. */ - this.appName_ = appName; - /** - * @private @const {?fireauth.constants.EmulatorSettings|undefined} - * The emulator configuration. - */ - this.emulatorConfig_ = emulatorConfig; - /** @private {?string} The client version. */ - this.clientVersion_ = opt_clientVersion || null; - /** @private {?string} The Auth endpoint ID. */ - this.endpointId_ = opt_endpointId || null; - // Delay RPC handler and iframe URL initialization until needed to ensure - // logged frameworks are propagated to the iframe. - /** @private {?string} The full client version string. */ - this.fullClientVersion_ = null; - /** @private {?string} The iframe URL. */ - this.iframeUrl_ = null; - /** @private {?fireauth.RpcHandler} The RPC handler for provided API key. */ - this.rpcHandler_ = null; - /** - * @private {!Array} The Auth event - * listeners. - */ - this.authEventListeners_ = []; - // Delay origin validator determination until needed, so the error is not - // thrown in the background. This will also prevent the getProjectConfig RPC - // until it is required. - /** @private {?goog.Promise} The origin validator. */ - this.originValidator_ = null; - /** @private {?goog.Promise} The initialization promise. */ - this.isInitialized_ = null; -}; - - -/** - * Validates the provided URL. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler used to validate the - * requested origin. - * @param {string=} opt_origin The optional page origin. If not provided, the - * window.location.href value is used. - * @return {!goog.Promise} The promise that resolves if the provided origin is - * valid. - * @private - */ -fireauth.iframeclient.IfcHandler.getOriginValidator_ = - function(rpcHandler, opt_origin) { - var origin = opt_origin || fireauth.util.getCurrentUrl(); - return rpcHandler.getAuthorizedDomains().then(function(authorizedDomains) { - if (!fireauth.util.isAuthorizedDomain(authorizedDomains, origin)) { - throw new fireauth.InvalidOriginError(fireauth.util.getCurrentUrl()); - } - }); -}; - - -/** - * Initializes the iframe client wrapper. - * @return {!goog.Promise} The promise that resolves on initialization. - */ -fireauth.iframeclient.IfcHandler.prototype.initialize = function() { - // Already initialized. - if (this.isInitialized_) { - return this.isInitialized_; - } - var self = this; - this.isInitialized_ = fireauth.util.onDomReady().then(function() { - /** - * @private {!fireauth.iframeclient.IframeWrapper} The iframe wrapper - * instance. - */ - self.iframeWrapper_ = new fireauth.iframeclient.IframeWrapper( - self.getIframeUrl()); - // Register all event listeners to Auth event messages sent from Auth - // iframe. - self.registerEvents_(); - }); - return this.isInitialized_; -}; - - -/** - * Waits for popup window to close. When closed start timeout listener for popup - * pending promise. If in the process, it was detected that the iframe does not - * support web storage, the popup is closed and the web storage unsupported - * error is thrown. - * @param {!Window} popupWin The popup window. - * @param {!function(!fireauth.AuthError)} onError The on error callback. - * @param {number} timeoutDuration The time to wait in ms after the popup is - * closed before triggering the popup closed by user error. - * @return {!goog.Promise} - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.startPopupTimeout = - function(popupWin, onError, timeoutDuration) { - // Expire pending timeout promise for popup operation. - var popupClosedByUserError = new fireauth.AuthError( - fireauth.authenum.Error.POPUP_CLOSED_BY_USER); - // If web storage is disabled in the iframe, expire popup timeout quickly with - // this error. - var webStorageNotSupportedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - var self = this; - var isResolved = false; - // Wait for the iframe to be ready first. - return this.initializeAndWait().then(function() { - // We do not return isWebStorageSupported() to ensure that this is backward - // compatible. - // Pushing the following client changes before updating the iframe to - // respond to these events would continue to work. - // The downside is that the popup could be closed before this resolves. - // In that case, they would get an error that the popup was closed and not - // the error that web storage is not supported, though that is unlikely - // as isWebStorageSupported should execute faster than the popup timeout. - // If web storage is not supported in the iframe, fail quickly. - self.isWebStorageSupported().then(function(isSupported) { - if (!isSupported) { - // If not supported, close window. - if (popupWin) { - fireauth.util.closeWindow(popupWin); - } - onError(webStorageNotSupportedError); - isResolved = true; - } - }); - }).thenCatch(function(error) { - // Ignore any possible error in iframe embedding. - // These types of errors will be handled in processPopup which will close - // the popup too if that happens. - return; - }).then(function() { - // Skip if already resolved. - if (isResolved) { - return; - } - // After the iframe is ready, wait for popup to close and then start timeout - // check. - return fireauth.util.onPopupClose(popupWin); - }).then(function() { - // Skip if already resolved. - if (isResolved) { - return; - } - return goog.Timer.promise(timeoutDuration).then(function() { - // If this is already resolved or rejected, this will do nothing. - onError(popupClosedByUserError); - }); - }); -}; - - -/** - * @return {boolean} Whether the handler should be initialized early. - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.shouldBeInitializedEarly = - function() { - var ua = fireauth.util.getUserAgentString(); - // Cannot run in the background (can't wait for iframe to be embedded - // before triggering popup redirect) and is Safari (can only detect - // localStorage in iframe via change event) => embed iframe ASAP. - // Do the same for mobile browsers on iOS devices as they use the same - // Safari implementation underneath. - return !fireauth.util.runsInBackground(ua) && - !fireauth.util.iframeCanSyncWebStorage(ua); -}; - - -/** - * @return {boolean} Whether the sign-in handler in the current environment - * has volatile session storage. - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.hasVolatileStorage = function() { - // Web environment with web storage enabled has stable sessionStorage. - return false; -}; - - -/** - * Processes the popup request. The popup instance must be provided externally - * and on error, the requestor must close the window. - * @param {?Window} popupWin The popup window reference. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {function()} onInitialize The function to call on initialization. - * @param {function(*)} onError The function to call on error. - * @param {string=} opt_eventId The optional event ID. - * @param {boolean=} opt_alreadyRedirected Whether popup is already redirected - * to final destination. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} The popup window promise. - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.processPopup = function( - popupWin, - mode, - provider, - onInitialize, - onError, - opt_eventId, - opt_alreadyRedirected, - opt_tenantId) { - // processPopup is failing since it tries to access popup win when tab can - // not run in background. For now bypass processPopup which runs - // additional origin check not accounted above. Besides, iframe will never - // hand result to parent if origin not whitelisted. - // Error thrown by browser: Unable to establish a connection with the - // popup. It may have been blocked by the browser. - // If popup is null, startPopupTimeout will catch it without having the - // above error getting triggered due to popup access from opener. - - // Reject immediately if the popup is blocked. - if (!popupWin) { - return goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.POPUP_BLOCKED)); - } - // Already redirected and cannot run in the background, resolve quickly while - // initializing. - if (opt_alreadyRedirected && !fireauth.util.runsInBackground()) { - // Initialize first before resolving. - this.initializeAndWait().thenCatch(function(error) { - fireauth.util.closeWindow(popupWin); - onError(error); - }); - onInitialize(); - // Already redirected. - return goog.Promise.resolve(); - } - // If origin validator not determined yet. - if (!this.originValidator_) { - this.originValidator_ = - fireauth.iframeclient.IfcHandler.getOriginValidator_( - this.getRpcHandler_()); - } - var self = this; - return this.originValidator_.then(function() { - // After origin validation, wait for iframe to be ready before redirecting. - var onReady = self.initializeAndWait().thenCatch(function(error) { - fireauth.util.closeWindow(popupWin); - onError(error); - throw error; - }); - onInitialize(); - return onReady; - }).then(function() { - // Popup and redirect operations work for OAuth providers only. - fireauth.AuthProvider.checkIfOAuthSupported(provider); - // Already redirected to intended destination, no need to redirect again. - if (opt_alreadyRedirected) { - return; - } - var oauthHelperWidgetUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - self.authDomain_, - self.apiKey_, - self.appName_, - mode, - provider, - null, - opt_eventId, - self.clientVersion_, - undefined, - self.endpointId_, - opt_tenantId, - self.emulatorConfig_); - // Redirect popup to OAuth helper widget URL. - fireauth.util.goTo(oauthHelperWidgetUrl, /** @type {!Window} */ (popupWin)); - }).thenCatch(function(e) { - // Force another origin validation. - if (e.code == 'auth/network-request-failed') { - self.originValidator_ = null; - } - throw e; - }); -}; - - -/** - * @return {!fireauth.RpcHandler} The RPC handler instance with the relevant - * endpoints, version and frameworks. - * @private - */ -fireauth.iframeclient.IfcHandler.prototype.getRpcHandler_ = function() { - if (!this.rpcHandler_) { - this.fullClientVersion_ = this.clientVersion_ ? - fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, - this.clientVersion_, - fireauth.iframeclient.OAuthUrlBuilder.getAuthFrameworksForApp_( - this.appName_)) : - null; - this.rpcHandler_ = new fireauth.RpcHandler( - this.apiKey_, - // Get the client Auth endpoint used. - fireauth.constants.getEndpointConfig(this.endpointId_), - this.fullClientVersion_); - if (this.emulatorConfig_) { - this.rpcHandler_.updateEmulatorConfig(this.emulatorConfig_); - } - } - return this.rpcHandler_; -}; - - -/** - * Processes the redirect request. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {?string=} opt_eventId The optional event ID. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.processRedirect = - function(mode, provider, opt_eventId, opt_tenantId) { - // If origin validator not determined yet. - if (!this.originValidator_) { - this.originValidator_ = - fireauth.iframeclient.IfcHandler.getOriginValidator_( - this.getRpcHandler_()); - } - var self = this; - // Make sure origin is validated. - return this.originValidator_.then(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - var oauthHelperWidgetUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - self.authDomain_, - self.apiKey_, - self.appName_, - mode, - provider, - fireauth.util.getCurrentUrl(), - opt_eventId, - self.clientVersion_, - undefined, - self.endpointId_, - opt_tenantId, - self.emulatorConfig_); - // Redirect to OAuth helper widget URL. - fireauth.util.goTo(oauthHelperWidgetUrl); - }).thenCatch(function(e) { - // Force another origin validation on network errors. - if (e.code == 'auth/network-request-failed') { - self.originValidator_ = null; - } - throw e; - }); -}; - - -/** @return {string} The iframe URL. */ -fireauth.iframeclient.IfcHandler.prototype.getIframeUrl = function() { - if (!this.iframeUrl_) { - this.iframeUrl_ = fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - this.authDomain_, - this.apiKey_, - this.appName_, - this.clientVersion_, - this.endpointId_, - fireauth.iframeclient.OAuthUrlBuilder.getAuthFrameworksForApp_( - this.appName_), - this.emulatorConfig_); - } - return this.iframeUrl_; -}; - - -/** - * @return {!goog.Promise} The promise that resolves when the iframe is ready. - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.initializeAndWait = function() { - // Initialize if not initialized yet. - var self = this; - return this.initialize().then(function() { - return self.iframeWrapper_.onReady(); - }).thenCatch(function(error) { - // Reset origin validator. - self.originValidator_ = null; - // Reject iframe ready promise with network error. - throw new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - }); -}; - - -/** - * @return {boolean} Whether the handler will unload the current page on - * redirect operations. - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.unloadsOnRedirect = function() { - return true; -}; - - -/** - * @param {string} authDomain The Firebase authDomain used to determine the - * OAuth helper page domain. - * @param {string} apiKey The API key for sending backend Auth requests. - * @param {string} appName The App ID for the Auth instance that triggered this - * request. - * @param {?string=} opt_clientVersion The optional client version string. - * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). - * @param {?Array=} opt_frameworks The optional list of framework IDs. - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @return {string} The data iframe src URL. - */ -fireauth.iframeclient.IfcHandler.getAuthIframeUrl = function(authDomain, apiKey, - appName, opt_clientVersion, opt_endpointId, opt_frameworks, emulatorConfig) { - // OAuth helper iframe URL. - var builder = new fireauth.iframeclient.IframeUrlBuilder( - authDomain, apiKey, appName, emulatorConfig); - return builder - .setVersion(opt_clientVersion) - .setEndpointId(opt_endpointId) - .setFrameworks(opt_frameworks) - .toString(); -}; - - -/** - * @param {string} authDomain The Firebase authDomain used to determine the - * OAuth helper page domain. - * @param {string} apiKey The API key for sending backend Auth requests. - * @param {string} appName The App ID for the Auth instance that triggered this - * request. - * @param {string} authType The type of operation that depends on OAuth sign in. - * @param {!fireauth.AuthProvider} provider The provider to sign in to. - * @param {?string=} opt_redirectUrl The optional URL to redirect to on OAuth - * sign in completion. - * @param {?string=} opt_eventId The optional event ID to identify on receipt. - * @param {?string=} opt_clientVersion The optional client version string. - * @param {?Object=} opt_additionalParams The optional - * additional parameters. - * @param {?string=} opt_endpointId The endpoint ID (staging, test Gaia, etc). - * @param {?string=} opt_tenantId The optional tenant ID. - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The emulator - * configuration. - * @return {string} The OAuth helper widget URL. - */ -fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl = function( - authDomain, - apiKey, - appName, - authType, - provider, - opt_redirectUrl, - opt_eventId, - opt_clientVersion, - opt_additionalParams, - opt_endpointId, - opt_tenantId, - emulatorConfig) { - // OAuth helper widget URL. - var builder = new fireauth.iframeclient.OAuthUrlBuilder( - authDomain, apiKey, appName, authType, provider, emulatorConfig); - return builder - .setRedirectUrl(opt_redirectUrl) - .setEventId(opt_eventId) - .setVersion(opt_clientVersion) - .setAdditionalParameters(opt_additionalParams) - .setEndpointId(opt_endpointId) - .setTenantId(opt_tenantId) - .toString(); -}; - - -/** - * Post message receiver event names. - * @enum {string} - */ -fireauth.iframeclient.IfcHandler.ReceiverEvent = { - AUTH_EVENT: 'authEvent' -}; - - -/** - * Post message sender event names. - * @enum {string} - */ -fireauth.iframeclient.IfcHandler.SenderEvent = { - WEB_STORAGE_SUPPORT_EVENT: 'webStorageSupport' -}; - - -/** - * Post message response field names. - * @enum {string} - */ -fireauth.iframeclient.IfcHandler.Response = { - STATUS: 'status', - AUTH_EVENT: 'authEvent', - WEB_STORAGE_SUPPORT: 'webStorageSupport' -}; - - -/** - * Post message status values. - * @enum {string} - */ -fireauth.iframeclient.IfcHandler.Status = { - ACK: 'ACK', - ERROR: 'ERROR' -}; - - -/** - * Registers all event listeners. - * @private - */ -fireauth.iframeclient.IfcHandler.prototype.registerEvents_ = function() { - // Should be run in initialization. - if (!this.iframeWrapper_) { - throw new Error('IfcHandler must be initialized!'); - } - var self = this; - // Listen to Auth change events emitted from iframe. - this.iframeWrapper_.registerEvent( - fireauth.iframeclient.IfcHandler.ReceiverEvent.AUTH_EVENT, - function(response) { - var resolveResponse = {}; - if (response && - response[fireauth.iframeclient.IfcHandler.Response.AUTH_EVENT]) { - var isHandled = false; - // Get Auth event (plain object). - var authEvent = fireauth.AuthEvent.fromPlainObject( - response[fireauth.iframeclient.IfcHandler.Response.AUTH_EVENT]); - // Trigger Auth change on all listeners. - for (var i = 0; i < self.authEventListeners_.length; i++) { - isHandled = self.authEventListeners_[i](authEvent) || isHandled; - } - // Return ack response to notify sender of success. - resolveResponse = {}; - resolveResponse[fireauth.iframeclient.IfcHandler.Response.STATUS] = - isHandled ? fireauth.iframeclient.IfcHandler.Status.ACK : - fireauth.iframeclient.IfcHandler.Status.ERROR; - return goog.Promise.resolve(resolveResponse); - } - // Return error status if the response is invalid. - resolveResponse[fireauth.iframeclient.IfcHandler.Response.STATUS] = - fireauth.iframeclient.IfcHandler.Status.ERROR; - return goog.Promise.resolve(resolveResponse); - }); -}; - - -/** - * @return {!goog.Promise} Whether web storage is supported in the - * iframe. - */ -fireauth.iframeclient.IfcHandler.prototype.isWebStorageSupported = function() { - var webStorageSupportEvent = - fireauth.iframeclient.IfcHandler.SenderEvent.WEB_STORAGE_SUPPORT_EVENT; - var message = { - 'type': webStorageSupportEvent - }; - var self = this; - // Initialize if not initialized yet. - return this.initialize().then(function() { - return self.iframeWrapper_.sendMessage(message); - }).then(function(response) { - // Parse the response and return the passed web storage support status. - var key = fireauth.iframeclient.IfcHandler.Response.WEB_STORAGE_SUPPORT; - if (response && - response.length && - typeof response[0][key] !== 'undefined') { - return response[0][key]; - } - // Internal error. - throw new Error; - }); -}; - - -/** - * @param {!function(?fireauth.AuthEvent):boolean} listener The Auth event - * listener to add. - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.addAuthEventListener = - function(listener) { - this.authEventListeners_.push(listener); -}; - - -/** - * @param {!function(?fireauth.AuthEvent):boolean} listener The Auth event - * listener to remove. - * @override - */ -fireauth.iframeclient.IfcHandler.prototype.removeAuthEventListener = - function(listener) { - goog.array.removeAllIf(this.authEventListeners_, function(ele) { - return ele == listener; - }); -}; diff --git a/packages/auth/src/iframeclient/iframewrapper.js b/packages/auth/src/iframeclient/iframewrapper.js deleted file mode 100644 index 92dbabea9cc..00000000000 --- a/packages/auth/src/iframeclient/iframewrapper.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines fireauth.iframeclient.IframeWrapper used to communicate - * with the hidden iframe to detect Auth events. - */ - -goog.provide('fireauth.iframeclient.IframeWrapper'); - -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.net.jsloader'); -goog.require('goog.string.Const'); - - -/** - * Defines the hidden iframe wrapper for cross origin communications. - * @param {string} url The hidden iframe src URL. - * @constructor - */ -fireauth.iframeclient.IframeWrapper = function(url) { - /** @private {string} The hidden iframe URL. */ - this.url_ = url; - - /** - * @type {?gapi.iframes.Iframe} - * @private - */ - this.iframe_ = null; - - /** @private {!goog.Promise} A promise that resolves on iframe open. */ - this.onIframeOpen_ = this.open_(); -}; - - -/** - * @typedef {{ - * type: string - * }} - */ -fireauth.iframeclient.IframeWrapper.Message; - -/** - * Returns URL, src of the hidden iframe. - * @return {string} - * @private - */ -fireauth.iframeclient.IframeWrapper.prototype.getPath_ = function() { - return this.url_; -}; - - -/** - * @return {!goog.Promise} The promise that resolves when the iframe is ready. - */ -fireauth.iframeclient.IframeWrapper.prototype.onReady = function() { - return this.onIframeOpen_; -}; - - -/** - * Returns options used to open the iframe. - * @return {!gapi.iframes.OptionsBag} - * @private - */ -fireauth.iframeclient.IframeWrapper.prototype.getOptions_ = function() { - var options = /** @type {!gapi.iframes.OptionsBag} */ ({ - 'where': document.body, - 'url': this.getPath_(), - 'messageHandlersFilter': /** @type {!gapi.iframes.IframesFilter} */ ( - fireauth.util.getObjectRef( - 'gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER')), - 'attributes': { - 'style': { - 'position': 'absolute', - 'top': '-100px', - 'width': '1px', - 'height': '1px' - } - }, - 'dontclear': true - }); - return options; -}; - - -/** - * Opens an iframe. - * @return {!goog.Promise} A promise that resolves on successful iframe open. - * @private - */ -fireauth.iframeclient.IframeWrapper.prototype.open_ = function() { - var self = this; - return fireauth.iframeclient.IframeWrapper.loadGApiJs_().then(function() { - return new goog.Promise(function(resolve, reject) { - /** - * @param {?gapi.iframes.Iframe} iframe The new opened iframe. - */ - var onOpen = function(iframe) { - self.iframe_ = iframe; - self.iframe_.restyle({ - // Prevent iframe from closing on mouse out. - 'setHideOnLeave': false - }); - // Confirm iframe is correctly loaded. - // To fallback on failure, set a timeout. - var networkErrorTimer = setTimeout(function() { - reject(new Error('Network Error')); - }, fireauth.iframeclient.IframeWrapper.PING_TIMEOUT_.get()); - // Clear timer and resolve pending iframe ready promise. - var clearTimerAndResolve = function() { - clearTimeout(networkErrorTimer); - resolve(); - }; - // This returns an IThenable. However the reject part does not call - // when the iframe is not loaded. - iframe.ping(clearTimerAndResolve).then( - clearTimerAndResolve, - function(error) { reject(new Error('Network Error')); }); - }; - /** @type {function():!gapi.iframes.Context} */ ( - fireauth.util.getObjectRef('gapi.iframes.getContext'))().open( - self.getOptions_(), onOpen); - }); - }); -}; - - -/** - * @param {!fireauth.iframeclient.IframeWrapper.Message} message to send. - * @return {!goog.Promise} The promise that resolve when message is - * sent. - */ -fireauth.iframeclient.IframeWrapper.prototype.sendMessage = function(message) { - var self = this; - return this.onIframeOpen_.then(function() { - return new goog.Promise(function(resolve, reject) { - self.iframe_.send( - message['type'], - message, - resolve, - /** @type {!gapi.iframes.IframesFilter} */ ( - fireauth.util.getObjectRef( - 'gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER'))); - - }); - }); -}; - - -/** - * Registers a listener to a post message. - * @param {string} eventName The message to register for. - * @param {gapi.iframes.MessageHandler} handler Message handler. - */ -fireauth.iframeclient.IframeWrapper.prototype.registerEvent = - function(eventName, handler) { - var self = this; - this.onIframeOpen_.then(function() { - self.iframe_.register( - eventName, - /** @type {function(this:gapi.iframes.Iframe, - * *, gapi.iframes.Iframe): *} - */ (handler), - /** @type {!gapi.iframes.IframesFilter} */ ( - fireauth.util.getObjectRef( - 'gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER'))); - }); -}; - - -/** - * Unregisters a listener to a post message. - * @param {string} eventName The message to unregister. - * @param {gapi.iframes.MessageHandler} handler Message handler. - */ -fireauth.iframeclient.IframeWrapper.prototype.unregisterEvent = - function(eventName, handler) { - var self = this; - this.onIframeOpen_.then(function() { - self.iframe_.unregister( - eventName, - /** @type {(function(this:gapi.iframes.Iframe, - * *, gapi.iframes.Iframe): *|undefined)} - */ (handler)); - }); -}; - - -/** @private @const {!goog.string.Const} The GApi loader URL. */ -fireauth.iframeclient.IframeWrapper.GAPI_LOADER_SRC_ = goog.string.Const.from( - 'https://apis.google.com/js/api.js?onload=%{onload}'); - - -/** - * @private @const {!fireauth.util.Delay} The gapi.load network error timeout - * delay with units in ms. - */ -fireauth.iframeclient.IframeWrapper.NETWORK_TIMEOUT_ = - new fireauth.util.Delay(30000, 60000); - - -/** - * @private @const {!fireauth.util.Delay} The iframe ping error timeout delay - * with units in ms. - */ -fireauth.iframeclient.IframeWrapper.PING_TIMEOUT_ = - new fireauth.util.Delay(5000, 15000); - - -/** @private {?goog.Promise} The cached GApi loader promise. */ -fireauth.iframeclient.IframeWrapper.cachedGApiLoader_ = null; - - -/** Resets the cached GApi loader. */ -fireauth.iframeclient.IframeWrapper.resetCachedGApiLoader = function() { - fireauth.iframeclient.IframeWrapper.cachedGApiLoader_ = null; -}; - - - -/** - * Loads the GApi client library if it is not loaded for gapi.iframes usage. - * @return {!goog.Promise} A promise that resolves when gapi.iframes is loaded. - * @private - */ -fireauth.iframeclient.IframeWrapper.loadGApiJs_ = function() { - // If already pending or resolved, return the cached promise. - if (fireauth.iframeclient.IframeWrapper.cachedGApiLoader_) { - return fireauth.iframeclient.IframeWrapper.cachedGApiLoader_; - } - // If there is no cached promise, initialize a new one. - fireauth.iframeclient.IframeWrapper.cachedGApiLoader_ = - new goog.Promise(function(resolve, reject) { - // Function to run when gapi.load is ready. - var onGapiLoad = function() { - // The developer may have tried to previously run gapi.load and failed. - // Run this to fix that. - fireauth.util.resetUnloadedGapiModules(); - var loader = /** @type {function(string, !Object)} */ ( - fireauth.util.getObjectRef('gapi.load')); - loader('gapi.iframes', { - 'callback': resolve, - 'ontimeout': function() { - // The above reset may be sufficient, but having this reset after - // failure ensures that if the developer calls gapi.load after the - // connection is re-established and before another attempt to embed - // the iframe, it would work and would not be broken because of our - // failed attempt. - // Timeout when gapi.iframes.Iframe not loaded. - fireauth.util.resetUnloadedGapiModules(); - reject(new Error('Network Error')); - }, - 'timeout': fireauth.iframeclient.IframeWrapper.NETWORK_TIMEOUT_.get() - }); - }; - if (fireauth.util.getObjectRef('gapi.iframes.Iframe')) { - // If gapi.iframes.Iframe available, resolve. - resolve(); - } else if (fireauth.util.getObjectRef('gapi.load')) { - // Gapi loader ready, load gapi.iframes. - onGapiLoad(); - } else { - // Create a new iframe callback when this is called so as not to overwrite - // any previous defined callback. This happens if this method is called - // multiple times in parallel and could result in the later callback - // overwriting the previous one. This would end up with a iframe - // timeout. - var cbName = '__iframefcb' + - Math.floor(Math.random() * 1000000).toString(); - // GApi loader not available, dynamically load platform.js. - goog.global[cbName] = function() { - // GApi loader should be ready. - if (fireauth.util.getObjectRef('gapi.load')) { - onGapiLoad(); - } else { - // Gapi loader failed, throw error. - reject(new Error('Network Error')); - } - }; - // Build GApi loader. - var url = goog.html.TrustedResourceUrl.format( - fireauth.iframeclient.IframeWrapper.GAPI_LOADER_SRC_, - {'onload': cbName}); - // Load GApi loader. - var result = goog.Promise.resolve(goog.net.jsloader.safeLoad(url)); - result.thenCatch(function(error) { - // In case library fails to load, typically due to a network error, - // reset cached loader to null to force a refresh on a retrial. - reject(new Error('Network Error')); - }); - } - }).thenCatch(function(error) { - // Reset cached promise to allow for retrial. - fireauth.iframeclient.IframeWrapper.cachedGApiLoader_ = null; - throw error; - }); - return fireauth.iframeclient.IframeWrapper.cachedGApiLoader_; -}; diff --git a/packages-exp/auth-exp/src/index.ts b/packages/auth/src/index.ts similarity index 100% rename from packages-exp/auth-exp/src/index.ts rename to packages/auth/src/index.ts diff --git a/packages/auth/src/messagechannel/defines.js b/packages/auth/src/messagechannel/defines.js deleted file mode 100644 index a58f513eac3..00000000000 --- a/packages/auth/src/messagechannel/defines.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the MessageChannel common utilities and enums. - */ - -goog.provide('fireauth.messagechannel.Error'); -goog.provide('fireauth.messagechannel.Status'); -goog.provide('fireauth.messagechannel.TimeoutDuration'); -goog.provide('fireauth.messagechannel.utils'); - - -/** - * Enum for the messagechannel error messages. These errors are not meant to be - * user facing. - * @enum {string} - */ -fireauth.messagechannel.Error = { - CONNECTION_CLOSED: 'connection_closed', - CONNECTION_UNAVAILABLE: 'connection_unavailable', - INVALID_RESPONSE: 'invalid_response', - TIMEOUT: 'timeout', - UNKNOWN: 'unknown_error', - UNSUPPORTED_EVENT: 'unsupported_event' -}; - - -/** - * Enum for the message channel request status labels. - * @enum {string} - */ -fireauth.messagechannel.Status = { - ACK: 'ack', - DONE: 'done' -}; - - -/** - * Enum for the timeout durations in milliseconds for different contexts. - * @enum {number} - */ -fireauth.messagechannel.TimeoutDuration = { - ACK: 50, - COMPLETION: 3000, - // Used when a handler is confirmed to be available on the other side. - LONG_ACK: 800 -}; - - -/** - * @param {?string=} opt_prefix An optional prefix string to prepend to ID. - * @param {?number=} opt_digits An optional number of digits used for event ID. - * @return {string} The generated event ID used to identify a generic event. - */ -fireauth.messagechannel.utils.generateEventId = - function(opt_prefix, opt_digits) { - // 0, null and undefined will default to 20. - var digits = opt_digits || 20; - return opt_prefix ? opt_prefix : '' + - Math.floor(Math.random() * Math.pow(10, digits)).toString(); -}; - - -/** - * @return {?MessageChannel} The initialized MessageChannel instance if - * supported. - */ -fireauth.messagechannel.utils.initializeMessageChannel = function() { - return typeof MessageChannel !== 'undefined' ? new MessageChannel() : null; -}; diff --git a/packages/auth/src/messagechannel/postmessager.js b/packages/auth/src/messagechannel/postmessager.js deleted file mode 100644 index cffe4e4cb9d..00000000000 --- a/packages/auth/src/messagechannel/postmessager.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the PostMessager interface needed for the - * `fireauth.messagechannel.Sender`, in addition to 2 types of implementations. - */ - -goog.provide('fireauth.messagechannel.PostMessager'); -goog.provide('fireauth.messagechannel.WindowPostMessager'); -goog.provide('fireauth.messagechannel.WorkerClientPostMessager'); - - -/** - * This is the interface defining the postMessage format of a window which - * takes an additional second parameter for target origin. - * - * @typedef {{ - * postMessage: function(*, string, !Array) - * }} - */ -fireauth.messagechannel.Window; - - -/** - * This is the interface defining the postMessage format of a worker or - * ServiceWorkerClient, etc. which just takes a message and a list of - * Transferables. - * - * @typedef {{ - * postMessage: function(*, !Array) - * }} - */ -fireauth.messagechannel.WorkerClient; - - -/** - * Defines a common interface to postMessage data to a specified PostMessager. - * @interface - */ -fireauth.messagechannel.PostMessager = function() {}; - - -/** - * Sends a message to the specified context. - * @param {*} message The message to send. - * @param {!Array} transfer The list of `Transferable` objects - * that are transferred with the message. The ownsership fo these objects is - * given to the destination side and they are no longer usable on the - * sending side. - */ -fireauth.messagechannel.PostMessager.prototype.postMessage = - function(message, transfer) {}; - - - -/** - * Defines the implementation for postMessaging to a window context. - * @param {!fireauth.messagechannel.Window} win The window PostMessager. - * @param {?string=} opt_targetOrigin The target origin. - * @constructor - * @implements {fireauth.messagechannel.PostMessager} - */ -fireauth.messagechannel.WindowPostMessager = function(win, opt_targetOrigin) { - /** - * @const @private {!fireauth.messagechannel.Window} The window PostMessager. - */ - this.win_ = win; - /** @const @private {string} The postMessage target origin. */ - this.targetOrigin_ = opt_targetOrigin || '*'; -}; - - -/** - * Sends a message to the specified window context. - * @param {*} message The message to send. - * @param {!Array} transfer The list of `Transferable` objects - * that are transferred with the message. The ownsership fo these objects is - * given to the destination side and they are no longer usable on the - * sending side. - * @override - */ -fireauth.messagechannel.WindowPostMessager.prototype.postMessage = - function(message, transfer) { - this.win_.postMessage(message, this.targetOrigin_, transfer); -}; - - -/** - * Defines the implementation for postMessaging to a worker/client context. - * @param {!fireauth.messagechannel.WorkerClient} worker The worker/client - * PostMessager. - * @constructor - * @implements {fireauth.messagechannel.PostMessager} - */ -fireauth.messagechannel.WorkerClientPostMessager = function(worker) { - /** - * @const @private {!fireauth.messagechannel.WorkerClient} The worker/client - * PostMessager. - */ - this.worker_ = worker; -}; - - -/** - * Sends a message to the specified worker/client context. - * @param {*} message The message to send. - * @param {!Array} transfer The list of `Transferable` objects - * that are transferred with the message. The ownsership fo these objects is - * given to the destination side and they are no longer usable on the - * sending side. - * @override - */ -fireauth.messagechannel.WorkerClientPostMessager.prototype.postMessage = - function(message, transfer) { - this.worker_.postMessage(message, transfer); -}; diff --git a/packages/auth/src/messagechannel/receiver.js b/packages/auth/src/messagechannel/receiver.js deleted file mode 100644 index e880b82efc0..00000000000 --- a/packages/auth/src/messagechannel/receiver.js +++ /dev/null @@ -1,223 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the MessageChannel based wrapper for receiving - * messages from other windows or workers. - */ - -goog.provide('fireauth.messagechannel.Receiver'); - -goog.require('fireauth.messagechannel.Status'); -goog.require('goog.Promise'); -goog.require('goog.array'); -goog.require('goog.object'); - - -/** - * Initializes a channel to receive specific messages from a specified event - * target. - * Note receivers should not be manually instantiated. Instead `getInstance()` - * should be used instead to get a receiver instance for a specified event - * target. - * @param {!EventTarget} eventTarget The event target to listen to. - * @constructor - */ -fireauth.messagechannel.Receiver = function(eventTarget) { - /** - * @const @private {!EventTarget} The messageChannel event target. - */ - this.eventTarget_ = eventTarget; - /** - * @const @private {!Object.|void>>} - * This is the event type to handlers hash map. It is used to hold the - * corresponding handlers for specified events. - */ - this.eventHandlers_ = {}; - /** - * @const @private {function(!Event)} The internal 'message' event handler - * used to reroute the request to corresponding subscribed handlers. - */ - this.messageEventHandler_ = goog.bind(this.handleEvent_, this); -}; - - -/** - * @param {!EventTarget} eventTarget The event target to check for. - * @return {boolean} Whether the receiver is listening to the specified event - * target. - */ -fireauth.messagechannel.Receiver.prototype.isListeningTo = - function(eventTarget) { - return this.eventTarget_ == eventTarget; -}; - - -/** - * @const @private {!Array} The list of all - * created `fireauth.messagechannel.Receiver` instances. - */ -fireauth.messagechannel.Receiver.receivers_ = []; - - -/** - * Return a receiver instance for the specified event target. This is needed - * since one instance can be available per event target. Otherwise receivers - * could clobber each other. - * @param {!EventTarget} eventTarget The event target to listen to. - * @return {!fireauth.messagechannel.Receiver} The receiver instance for the - * specified event target. - */ -fireauth.messagechannel.Receiver.getInstance = function(eventTarget) { - // The results are stored in an array since objects can't be keys for other - // objects. In addition, setting a unique property on an event target as a - // hash map key may not be allowed due to CORS restrictions. - var instance; - goog.array.forEach( - fireauth.messagechannel.Receiver.receivers_, - function(receiver) { - if (receiver.isListeningTo(eventTarget)) { - instance = receiver; - } - }); - if (!instance) { - instance = new fireauth.messagechannel.Receiver(eventTarget); - fireauth.messagechannel.Receiver.receivers_.push(instance); - } - return instance; -}; - - -/** - * Handles a PostMessage event based on the following protocol: - *
    - *
  • When an event is first detected, check there is a subscribed handler. - * If not, do nothing as there could be other listeners.
  • - *
  • If there is a subscribed event, reply with an ACK event to notify the - * sender that the event can be handled.
  • - *
  • Trigger the subscribed handlers.
  • - *
  • Reply again with the combined results of all subscribed handlers and - * return the response back.
  • - *
- * - * @param {!Event} event The PostMessage event to handle. - * @private - */ -fireauth.messagechannel.Receiver.prototype.handleEvent_ = function(event) { - // Respond to sender first with ack reply. This will let the client - // know that the service worker can handle this event. - var eventType = event.data['eventType']; - var eventId = event.data['eventId']; - var handlers = this.eventHandlers_[eventType]; - if (handlers && handlers.length > 0) { - // Event can be handled. - event.ports[0].postMessage({ - 'status': fireauth.messagechannel.Status.ACK, - 'eventId': eventId, - 'eventType': eventType, - 'response': null - }); - var promises = []; - goog.array.forEach(handlers, function(handler) { - // Wrap in promise in case the handler doesn't return a promise. - promises.push(goog.Promise.resolve().then(function() { - return handler(event.origin, event.data['data']); - })); - }); - // allSettled is more flexible as it executes all the promises passed and - // returns whether they succeeded or failed. - goog.Promise.allSettled(promises) - .then(function(result) { - // allResponse has the format: - // !Array - // Respond to sender with ack reply. - // De-obfuscate the allSettled result. - var allResponses = []; - goog.array.forEach(result, function(item) { - allResponses.push({ - 'fulfilled': item.fulfilled, - 'value': item.value, - // Error cannot be clone in postMessage. - 'reason': item.reason ? item.reason.message : undefined - }); - }); - // Remove undefined fields. - goog.array.forEach(allResponses, function(item) { - for (var key in item) { - if (typeof item[key] === 'undefined') { - delete item[key]; - } - } - }); - event.ports[0].postMessage({ - 'status': fireauth.messagechannel.Status.DONE, - 'eventId': eventId, - 'eventType': eventType, - 'response': allResponses - }); - }); - } - // Let unsupported events time out, as there could be external receivers - // that can handle them. -}; - - -/** - * Subscribes to events of the specified type. - * @param {string} eventType The event type to listen to. - * @param {function(string, *):!goog.Promise|void} handler The async callback - * function to run when the event is triggered. - */ -fireauth.messagechannel.Receiver.prototype.subscribe = - function(eventType, handler) { - if (goog.object.isEmpty(this.eventHandlers_)) { - this.eventTarget_.addEventListener('message', this.messageEventHandler_); - } - if (typeof this.eventHandlers_[eventType] === 'undefined') { - this.eventHandlers_[eventType] = []; - } - this.eventHandlers_[eventType].push(handler); -}; - - -/** - * Unsubscribes the specified handler from the specified event. If no handler - * is specified, all handlers are unsubscribed. - * @param {string} eventType The event type to unsubscribe from. - * @param {?function(string, *):!goog.Promise|void=} opt_handler The - * callback function to unsubscribe from the specified event type. If none - * is specified, all handlers are unsubscribed. - */ -fireauth.messagechannel.Receiver.prototype.unsubscribe = - function(eventType, opt_handler) { - if (typeof this.eventHandlers_[eventType] !== 'undefined' && opt_handler) { - goog.array.removeAllIf(this.eventHandlers_[eventType], function(ele) { - return ele == opt_handler; - }); - if (this.eventHandlers_[eventType].length == 0) { - delete this.eventHandlers_[eventType]; - } - } else if (!opt_handler) { - // Unsubscribe all handlers for speficied event. - delete this.eventHandlers_[eventType]; - } - if (goog.object.isEmpty(this.eventHandlers_)) { - this.eventTarget_.removeEventListener('message', this.messageEventHandler_); - } -}; diff --git a/packages/auth/src/messagechannel/sender.js b/packages/auth/src/messagechannel/sender.js deleted file mode 100644 index 6775de1321e..00000000000 --- a/packages/auth/src/messagechannel/sender.js +++ /dev/null @@ -1,241 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the MessageChannel based wrapper for sending messages - * to other windows or workers. - */ - -goog.provide('fireauth.messagechannel.Sender'); - -goog.require('fireauth.messagechannel.Error'); -goog.require('fireauth.messagechannel.PostMessager'); -goog.require('fireauth.messagechannel.Status'); -goog.require('fireauth.messagechannel.TimeoutDuration'); -goog.require('fireauth.messagechannel.utils'); -goog.require('goog.Promise'); -goog.require('goog.array'); - - -/** - * This is the interface defining a MessageChannel/handler pair. - * - * @typedef {{ - * onMessage: function(!Event), - * messageChannel: !MessageChannel - * }} - */ -fireauth.messagechannel.MessageHandler; - - -/** - * Helper static function to create messageChannel errors. - * @param {!fireauth.messagechannel.Error} errorId The error identifier. - * @param {?string=} opt_message The optional error message used for generic - * error types. - * @return {!Error} The constructed error to return. - * @private - */ -fireauth.messagechannel.createError_ = function(errorId, opt_message) { - if (errorId != fireauth.messagechannel.Error.UNKNOWN || !opt_message) { - return new Error(errorId); - } else { - return new Error(opt_message); - } -}; - - -/** - * Initializes a channel to send specific messages to a specified PostMessage. - * @param {!fireauth.messagechannel.PostMessager} postMessager The post messager - * to send messages to. - * @constructor - */ -fireauth.messagechannel.Sender = function(postMessager) { - /** - * @const @private {!fireauth.messagechannel.PostMessager} The messageChannel - * PostMessager. - */ - this.postMessager_ = postMessager; - /** @private {boolean} Whether the connection was closed. */ - this.closed_ = false; - /** - * @const @private {!Array} The list - * of subscribed message handlers and their corresponding MessageChannels. - */ - this.messageHandlers_ = []; -}; - - -/** - * Sends a message to the receiver. The message is identified by an event - * type and can carry additional payload data. - * The sender protocol works as follows: - *
    - *
  • The request is constructed and postMessaged to the receiver with the port - * used to reply back to sender.
  • - *
  • The operation will block until an ACK response is received. If not, it - * will timeout and reject with an error.
  • - *
  • If an ACK response is received, it will wait longer for the full - * processed response.
  • - *
  • Once the response is received, the operation will resolve with that - * result.
  • - *
- * - * @param {string} eventType The event type identifying the message. This is - * used to help the receiver handle this message. - * @param {?Object=} opt_data The optional data to send along the message. - * @param {?boolean=} opt_useLongTimeout Whether long timeout should be used - * for ACK responses. - * @return {!goog.Promise>} A promise that - * resolves with the receiver responses. - */ -fireauth.messagechannel.Sender.prototype.send = function( - eventType, opt_data, opt_useLongTimeout) { - var self = this; - var eventId; - var data = opt_data || {}; - var onMessage; - var ackTimer; - var completionTimer; - var entry = null; - if (this.closed_) { - return goog.Promise.reject(fireauth.messagechannel.createError_( - fireauth.messagechannel.Error.CONNECTION_UNAVAILABLE)); - } - var ackTimeout = - !!opt_useLongTimeout ? - fireauth.messagechannel.TimeoutDuration.LONG_ACK : - fireauth.messagechannel.TimeoutDuration.ACK; - var messageChannel = - fireauth.messagechannel.utils.initializeMessageChannel(); - return new goog.Promise(function(resolve, reject) { - // Send message along with port for reply - if (messageChannel) { - eventId = fireauth.messagechannel.utils.generateEventId(); - // Start the connection if not already started. - messageChannel.port1.start(); - // Handler for receiving message reply from receiver. - // Blocks promise resolution until service worker detects the change. - ackTimer = setTimeout(function() { - // The receiver may not be able to handle the response for various - // reasons: library not included, or an incompatible version of - // the library is included. - // Timeout after some time. - reject(fireauth.messagechannel.createError_( - fireauth.messagechannel.Error.UNSUPPORTED_EVENT)); - }, ackTimeout); - onMessage = function(event) { - // Process only the expected events that match current event ID. - if (event.data['eventId'] !== eventId) { - return; - } - // This avoids adding a long wait when the receiver is unable to handle - // the event. - if (event.data['status'] === fireauth.messagechannel.Status.ACK) { - clearTimeout(ackTimer); - // Set longer timeout to allow receiver to process. - completionTimer = setTimeout(function() { - reject(fireauth.messagechannel.createError_( - fireauth.messagechannel.Error.TIMEOUT)); - }, fireauth.messagechannel.TimeoutDuration.COMPLETION); - return; - } else if (event.data['status'] === - fireauth.messagechannel.Status.DONE) { - clearTimeout(completionTimer); - if (typeof event.data['response'] !== 'undefined') { - resolve(event.data['response']); - } else { - reject(fireauth.messagechannel.createError_( - fireauth.messagechannel.Error.UNKNOWN)); - } - } else { - clearTimeout(ackTimer); - clearTimeout(completionTimer); - reject(fireauth.messagechannel.createError_( - fireauth.messagechannel.Error.INVALID_RESPONSE)); - } - }; - entry = { - 'messageChannel': messageChannel, - 'onMessage': onMessage - }; - self.messageHandlers_.push(entry); - messageChannel.port1.addEventListener('message', onMessage); - var request = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - // It is possible the receiver cannot handle this result. - // For example, the developer may not be including the library in the - // receiver or using an outdated version. - self.postMessager_.postMessage( - request, - [messageChannel.port2]); - } else { - // No connection available. - reject(fireauth.messagechannel.createError_( - fireauth.messagechannel.Error.CONNECTION_UNAVAILABLE)); - } - }).then(function(result) { - // On completion, remove the message handler. A new one is needed for a - // new message. - self.removeMessageHandler_(entry); - return result; - }).thenCatch(function(error) { - // On failure, remove the message handler. A new one is needed for a new - // message. - self.removeMessageHandler_(entry); - throw error; - }); -}; - - -/** - * Removes the onMessage handler for the specified messageChannel. - * @param {?fireauth.messagechannel.MessageHandler} messageHandler - * @private - */ -fireauth.messagechannel.Sender.prototype.removeMessageHandler_ = - function(messageHandler) { - if (!messageHandler) { - return; - } - var messageChannel = messageHandler['messageChannel']; - var onMessage = messageHandler['onMessage']; - if (messageChannel) { - messageChannel.port1.removeEventListener('message', onMessage); - messageChannel.port1.close(); - } - goog.array.removeAllIf(this.messageHandlers_, function(ele) { - return ele == messageHandler; - }); -}; - - -/** Closes the underlying MessageChannel connection. */ -fireauth.messagechannel.Sender.prototype.close = function() { - // Any pending event will timeout. - while (this.messageHandlers_.length > 0) { - this.removeMessageHandler_(this.messageHandlers_[0]); - } - this.closed_ = true; -}; - diff --git a/packages-exp/auth-exp/src/mfa/index.ts b/packages/auth/src/mfa/index.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/index.ts rename to packages/auth/src/mfa/index.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_assertion.ts b/packages/auth/src/mfa/mfa_assertion.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_assertion.ts rename to packages/auth/src/mfa/mfa_assertion.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_error.ts b/packages/auth/src/mfa/mfa_error.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_error.ts rename to packages/auth/src/mfa/mfa_error.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_info.test.ts b/packages/auth/src/mfa/mfa_info.test.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_info.test.ts rename to packages/auth/src/mfa/mfa_info.test.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_info.ts b/packages/auth/src/mfa/mfa_info.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_info.ts rename to packages/auth/src/mfa/mfa_info.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_resolver.test.ts b/packages/auth/src/mfa/mfa_resolver.test.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_resolver.test.ts rename to packages/auth/src/mfa/mfa_resolver.test.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_resolver.ts b/packages/auth/src/mfa/mfa_resolver.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_resolver.ts rename to packages/auth/src/mfa/mfa_resolver.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_session.test.ts b/packages/auth/src/mfa/mfa_session.test.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_session.test.ts rename to packages/auth/src/mfa/mfa_session.test.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_session.ts b/packages/auth/src/mfa/mfa_session.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_session.ts rename to packages/auth/src/mfa/mfa_session.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_user.test.ts b/packages/auth/src/mfa/mfa_user.test.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_user.test.ts rename to packages/auth/src/mfa/mfa_user.test.ts diff --git a/packages-exp/auth-exp/src/mfa/mfa_user.ts b/packages/auth/src/mfa/mfa_user.ts similarity index 100% rename from packages-exp/auth-exp/src/mfa/mfa_user.ts rename to packages/auth/src/mfa/mfa_user.ts diff --git a/packages-exp/auth-exp/src/model/application_verifier.ts b/packages/auth/src/model/application_verifier.ts similarity index 100% rename from packages-exp/auth-exp/src/model/application_verifier.ts rename to packages/auth/src/model/application_verifier.ts diff --git a/packages-exp/auth-exp/src/model/auth.ts b/packages/auth/src/model/auth.ts similarity index 100% rename from packages-exp/auth-exp/src/model/auth.ts rename to packages/auth/src/model/auth.ts diff --git a/packages-exp/auth-exp/src/model/enum_maps.ts b/packages/auth/src/model/enum_maps.ts similarity index 100% rename from packages-exp/auth-exp/src/model/enum_maps.ts rename to packages/auth/src/model/enum_maps.ts diff --git a/packages-exp/auth-exp/src/model/enums.ts b/packages/auth/src/model/enums.ts similarity index 100% rename from packages-exp/auth-exp/src/model/enums.ts rename to packages/auth/src/model/enums.ts diff --git a/packages-exp/auth-exp/src/model/id_token.ts b/packages/auth/src/model/id_token.ts similarity index 100% rename from packages-exp/auth-exp/src/model/id_token.ts rename to packages/auth/src/model/id_token.ts diff --git a/packages-exp/auth-exp/src/model/popup_redirect.ts b/packages/auth/src/model/popup_redirect.ts similarity index 100% rename from packages-exp/auth-exp/src/model/popup_redirect.ts rename to packages/auth/src/model/popup_redirect.ts diff --git a/packages-exp/auth-exp/src/model/public_types.ts b/packages/auth/src/model/public_types.ts similarity index 99% rename from packages-exp/auth-exp/src/model/public_types.ts rename to packages/auth/src/model/public_types.ts index c7a24f18572..92adb411a0c 100644 --- a/packages-exp/auth-exp/src/model/public_types.ts +++ b/packages/auth/src/model/public_types.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { FirebaseApp } from '@firebase/app'; import { CompleteFn, ErrorFn, @@ -170,12 +171,14 @@ export interface AuthSettings { * @public */ export interface Auth { - /** The name of the app associated with the Auth service instance. */ + /** The {@link @firebase/app#FirebaseApp} associated with the `Auth` service instance. */ + readonly app: FirebaseApp; + /** The name of the app associated with the `Auth` service instance. */ readonly name: string; /** The {@link Config} used to initialize this instance. */ readonly config: Config; /** - * Changes the type of persistence on the {@link Auth} instance. + * Changes the type of persistence on the `Auth` instance. * * @remarks * This will affect the currently saved Auth session and applies this type of persistence for @@ -1095,7 +1098,7 @@ export interface PopupRedirectResolver {} declare module '@firebase/component' { interface NameServiceMapping { - 'auth-exp': Auth; + 'auth': Auth; } } diff --git a/packages-exp/auth-exp/src/model/user.ts b/packages/auth/src/model/user.ts similarity index 100% rename from packages-exp/auth-exp/src/model/user.ts rename to packages/auth/src/model/user.ts diff --git a/packages/auth/src/multifactorassertion.js b/packages/auth/src/multifactorassertion.js deleted file mode 100644 index d09de2a60de..00000000000 --- a/packages/auth/src/multifactorassertion.js +++ /dev/null @@ -1,226 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the `firebase.auth.MultiFactorAssertion` abstract class - * and all its subclasses, such as PhoneMultiFactorAssertion. - */ - -goog.provide('fireauth.AuthCredentialMultiFactorAssertion'); -goog.provide('fireauth.MultiFactorAssertion'); -goog.provide('fireauth.PhoneMultiFactorAssertion'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.MultiFactorAuthCredential'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.object'); - - -/** - * Abstract class representing a `firebase.auth.MultiFactorAssertion` interface. - * This is used to facilitate enrollment of a second factor on an existing user - * or sign-in of a user who already verified the first factor. - * @abstract - * @constructor - */ -fireauth.MultiFactorAssertion = function() {}; - - -/** - * Finalizes the 2nd factor enrollment flow with the current - * MultiFactorAssertion. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorEnrollmentRequestIdentifier} enrollmentRequest - * The enrollment request identifying the user. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the updated ID and refresh tokens. - * @protected - */ -fireauth.MultiFactorAssertion.prototype.finalizeEnrollmentWithVerificationInfo = - goog.abstractMethod; - - -/** - * Finalizes the 2nd factor sign-in flow with the current MultiFactorAssertion. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorSignInRequestIdentifier} signInRequest - * The sign-in request identifying the user. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the signed in user's ID and refresh tokens. - * @protected - */ -fireauth.MultiFactorAssertion.prototype.finalizeSignInWithVerificationInfo = - goog.abstractMethod; - - -/** - * Processes the `MultiFactorAssertion` instance using the `MultiFactorSession` - * provided and optional display name for enrollment flows. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorSession} session The multi-factor session - * instance. - * @param {?string=} displayName The optional display name for a multi-factor - * enrollment. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the signed in or enrolled user's ID and refresh - * tokens. - */ -fireauth.MultiFactorAssertion.prototype.process = - function(rpcHandler, session, displayName) { - // Session obtained from user in enroll. - // It is obtained from error in resolver. - if (session.type == fireauth.MultiFactorSession.Type.ENROLL) { - return this.finalizeMfaEnrollment_(rpcHandler, session, displayName); - } else { - return this.finalizeMfaSignIn_(rpcHandler, session); - } -}; - - -/** - * Finalizes the multi-factor enrollment. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorSession} session The multi-factor session for - * the current signed in user. - * @param {?string=} displayName The optional display name for a multi-factor - * enrollment. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the enrolled user's ID and refresh tokens. - * @private - */ -fireauth.MultiFactorAssertion.prototype.finalizeMfaEnrollment_ = - function(rpcHandler, session, displayName) { - var self = this; - return session.getRawSession().then(function(rawSession) { - var request = { - 'idToken': rawSession - }; - if (typeof displayName !== 'undefined') { - request['displayName'] = displayName; - } - return self.finalizeEnrollmentWithVerificationInfo(rpcHandler, request); - }); -}; - - -/** - * Finalizes the multi-factor sign-in. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorSession} session The multi-factor session for - * the multi-factor enrolled user trying to sign-in. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the signed in user's ID and refresh tokens. - * @private - */ -fireauth.MultiFactorAssertion.prototype.finalizeMfaSignIn_ = - function(rpcHandler, session) { - var self = this; - return session.getRawSession().then(function(rawSession) { - var request = { - 'mfaPendingCredential': rawSession, - }; - return self.finalizeSignInWithVerificationInfo(rpcHandler, request); - }); -}; - - -/** - * Defines a class for handling MultiFactorAssertions based on - * `MultiFactorAuthCredentials`. - * @param {!fireauth.MultiFactorAuthCredential} multiFactorAuthCredential The - * multi-factor AuthCredential. - * @constructor - * @extends {fireauth.MultiFactorAssertion} - */ -fireauth.AuthCredentialMultiFactorAssertion = - function(multiFactorAuthCredential) { - // This assumes the factor ID matches the credential providerId. - // If this is ever not true, the subclass can overwrite that. - fireauth.object.setReadonlyProperty( - this, 'factorId', multiFactorAuthCredential.providerId); - /** - * @protected {!fireauth.MultiFactorAuthCredential} The underlying - * multi-factor AuthCredential. - */ - this.multiFactorAuthCredential = multiFactorAuthCredential; -}; -goog.inherits( - fireauth.AuthCredentialMultiFactorAssertion, fireauth.MultiFactorAssertion); - - -/** - * Finalizes the 2nd factor enrollment flow with the current - * MultiFactorAssertion. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorEnrollmentRequestIdentifier} enrollmentRequest - * The enrollment request identifying the user. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the updated ID and refresh tokens. - * @protected - * @override - */ -fireauth.AuthCredentialMultiFactorAssertion.prototype - .finalizeEnrollmentWithVerificationInfo = function(rpcHandler, - enrollmentRequest) { - return this.multiFactorAuthCredential.finalizeMfaEnrollment( - rpcHandler, enrollmentRequest); -}; - - -/** - * Finalizes the 2nd factor sign-in flow with the current MultiFactorAssertion. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorSignInRequestIdentifier} signInRequest - * The sign-in request identifying the user. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the signed in user's ID and refresh tokens. - * @protected - * @override - */ -fireauth.AuthCredentialMultiFactorAssertion.prototype - .finalizeSignInWithVerificationInfo = function(rpcHandler, - signInRequest) { - return this.multiFactorAuthCredential.finalizeMfaSignIn( - rpcHandler, signInRequest); -}; - - -/** - * Defines a class for handling MultiFactorAssertions based on - * PhoneAuthCredentials. This class extends `AuthCredentialMultiFactorAssertion` - * but for `PhoneAuthCredentials` only. - * @param {!fireauth.PhoneAuthCredential} phoneAuthCredential - * @constructor - * @extends {fireauth.AuthCredentialMultiFactorAssertion} - */ -fireauth.PhoneMultiFactorAssertion = function(phoneAuthCredential) { - fireauth.PhoneMultiFactorAssertion.base( - this, 'constructor', phoneAuthCredential); - // This class supports phone credentials only. - if (this.multiFactorAuthCredential.providerId != - fireauth.PhoneAuthProvider['PROVIDER_ID']) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'firebase.auth.PhoneMultiFactorAssertion requires a valid ' + - 'firebase.auth.PhoneAuthCredential'); - } -}; -goog.inherits( - fireauth.PhoneMultiFactorAssertion, - fireauth.AuthCredentialMultiFactorAssertion); diff --git a/packages/auth/src/multifactorauthcredential.js b/packages/auth/src/multifactorauthcredential.js deleted file mode 100644 index 64794397e24..00000000000 --- a/packages/auth/src/multifactorauthcredential.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the `fireauth.MultiFactorAuthCredential` interface. - * This is used for `firebase.auth.AuthCredentials` that can be used for 2nd - * factor sign-in too. - */ - -goog.provide('fireauth.MultiFactorAuthCredential'); -goog.provide('fireauth.MultiFactorEnrollmentRequestIdentifier'); -goog.provide('fireauth.MultiFactorSignInRequestIdentifier'); - -goog.forwardDeclare('fireauth.AuthCredential'); - - -/** - * Type specifying the EnrollmentRequest interface to be passed along the - * AuthCredential's verificationInfo. This is used to identify the user - * enrolling the factor and additional 2nd factor information such as the - * display name. - * @typedef {{ - * idToken: string, - * displayName: (?string|undefined) - * }} - */ -fireauth.MultiFactorEnrollmentRequestIdentifier; - - -/** - * Type specifying the SignInRequest interface to be passed along the - * AuthCredential's verificationInfo. This is used to identify the user - * who already verified the first factor. - * @typedef {{ - * mfaPendingCredential: string - * }} - */ -fireauth.MultiFactorSignInRequestIdentifier; - - -/** - * Interface representing AuthCredentials that can also be used as second factor - * credentials. - * @extends {fireauth.AuthCredential} - * @interface - */ -fireauth.MultiFactorAuthCredential = function() {}; - - -/** - * The credential provider ID. - * @type {string} The provider ID. - */ -fireauth.MultiFactorAuthCredential.prototype.providerId; - - -/** - * Finalizes the 2nd factor enrollment flow with the current AuthCredential - * using the enrollment request identifier. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorEnrollmentRequestIdentifier} enrollmentRequest - * The enrollment request identifying the user. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the updated ID and refresh tokens. - */ -fireauth.MultiFactorAuthCredential.prototype.finalizeMfaEnrollment = - function(rpcHandler, enrollmentRequest) {}; - - -/** - * Finalizes the 2nd factor sign-in flow with the current AuthCredential - * using the sign-in request identifier. - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler instance. - * @param {!fireauth.MultiFactorSignInRequestIdentifier} signInRequest - * The sign-in request identifying the user. - * @return {!goog.Promise<{idToken: string, refreshToken: string}>} A promise - * that resolves with the signed in user's ID and refresh tokens. - */ -fireauth.MultiFactorAuthCredential.prototype.finalizeMfaSignIn = - function(rpcHandler, signInRequest) {}; diff --git a/packages/auth/src/multifactorerror.js b/packages/auth/src/multifactorerror.js deleted file mode 100644 index 336fe65d101..00000000000 --- a/packages/auth/src/multifactorerror.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the MultiFactorError class, a subclass of - * fireauth.AuthError. - */ - - -goog.provide('fireauth.MultiFactorError'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.MultiFactorResolver'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.object'); -goog.require('goog.object'); - - -/** - * Multi-factor error with resolver, used to resolve sign-in after a two-factor - * user signs in with a first factor and is required to prove ownership of the - * second factor. - * @param {!fireauth.Auth} auth The Auth instance. - * @param {!fireauth.MultiFactorResolver.ErrorResponse} errorResponse The server - * error response containing the pending multi-factor credential. - * @param {function({idToken: string, refreshToken: string}): - * !goog.Promise} onIdTokenResolver - * A function that takes the assertion token response and any previous - * information returned with the error and completes sign in with a - * `UserCredential`. - * @param {string=} message The optional custom human-readable message. If not - * provided, a default message will be used. - * @constructor - * @extends {fireauth.AuthError} - */ -fireauth.MultiFactorError = function( - auth, errorResponse, onIdTokenResolver, message) { - fireauth.MultiFactorError.base( - this, - 'constructor', - fireauth.authenum.Error.MFA_REQUIRED, - message, - errorResponse); - this.serverResponse_ = goog.object.clone(errorResponse); - /** - * @const @private {!fireauth.MultiFactorResolver} The multi-factor resolver - * instance. - */ - this.resolver_ = - new fireauth.MultiFactorResolver(auth, errorResponse, onIdTokenResolver); - fireauth.object.setReadonlyProperty(this, 'resolver', this.resolver_); -}; -goog.inherits(fireauth.MultiFactorError, fireauth.AuthError); - - -/** - * Initializes a `MultiFactorError` from the plain object provided. If the - * object is not a valid `MultiFactorError`, null is returned. - * @param {?Object|undefined} response The object response to convert to a - * fireauth.MultiFactorError. - * @param {!fireauth.Auth} auth The Auth instance. - * @param {function({idToken: string, refreshToken: string}): - * !goog.Promise} onIdTokenResolver - * A function that takes the assertion token response and any previous - * information returned with the error and completes sign in with a - * `UserCredential`. - * @return {?fireauth.MultiFactorError} The `MultiFactorError` error - * representation of the response. null is returned if the response is not - * a valid MultiFactorError plain object representation. - */ -fireauth.MultiFactorError.fromPlainObject = - function(response, auth, onIdTokenResolver) { - if (response && - goog.isObject(response['serverResponse']) && - response['code'] === 'auth/' + fireauth.authenum.Error.MFA_REQUIRED) { - try { - return new fireauth.MultiFactorError( - auth, - /** @type {!fireauth.MultiFactorResolver.ErrorResponse} */ ( - response['serverResponse']), - onIdTokenResolver, - response['message']); - } catch (e) { - return null; - } - } - return null; -}; diff --git a/packages/auth/src/multifactorgenerator.js b/packages/auth/src/multifactorgenerator.js deleted file mode 100644 index 869e77c5cd0..00000000000 --- a/packages/auth/src/multifactorgenerator.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the MultiFactorGenerators used to generate - * MultiFactorAssertions. This currently covers only PhoneMultiFactorGenerator. - */ - -goog.provide('fireauth.PhoneMultiFactorGenerator'); - -goog.require('fireauth.PhoneMultiFactorAssertion'); -goog.require('fireauth.constants'); -goog.require('fireauth.object'); - - -/** - * Defines the multi-factor generator for PhoneMultiFactorAssertions. - * This class acts only as a namespace and defines some static methods and - * properties. - * @constructor @struct @final - */ -fireauth.PhoneMultiFactorGenerator = function() {}; -fireauth.object.setReadonlyProperty(fireauth.PhoneMultiFactorGenerator, - 'FACTOR_ID', fireauth.constants.SecondFactorType.PHONE); - - -/** - * Initializes a `PhoneMultiFactorAssertion` given a `PhoneAuthCredential`. - * @param {!fireauth.PhoneAuthCredential} phoneAuthCredential - * @return {!fireauth.PhoneMultiFactorAssertion} The `MultiFactorAssertion` - * corresponding to the provided `PhoneAuthCredential`. - */ -fireauth.PhoneMultiFactorGenerator.assertion = function(phoneAuthCredential) { - return new fireauth.PhoneMultiFactorAssertion(phoneAuthCredential); -}; diff --git a/packages/auth/src/multifactorinfo.js b/packages/auth/src/multifactorinfo.js deleted file mode 100644 index 3f7797b8bb2..00000000000 --- a/packages/auth/src/multifactorinfo.js +++ /dev/null @@ -1,214 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the multi-factor enrollment information. - */ - -goog.provide('fireauth.MultiFactorInfo'); -goog.provide('fireauth.PhoneMultiFactorInfo'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.object'); - - -/** - * Abstract class representing a `firebase.auth.MultiFactorInfo` interface. - * This is typically parsed from a server response. - * @param {?Object} resp The server response. - * @abstract - * @constructor - */ -fireauth.MultiFactorInfo = function(resp) { - var factorId = resp && this.getFactorId(resp); - if (factorId && resp && - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.MFA_ENROLLMENT_ID]) { - fireauth.object.setReadonlyProperty( - this, - 'uid', - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.MFA_ENROLLMENT_ID]); - fireauth.object.setReadonlyProperty( - this, - 'displayName', - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.DISPLAY_NAME] || null); - var enrollmentTime = null; - // Encoded using [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. - // For example, "2017-01-15T01:30:15.01Z". - // This can be parsed directly in modern browsers via Date constructor. - // This can be computed using Data.prototype.toISOString. - if (resp[fireauth.MultiFactorInfo.MfaEnrollmentField.ENROLLED_AT]) { - enrollmentTime = new Date( - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.ENROLLED_AT]) - .toUTCString(); - } - fireauth.object.setReadonlyProperty( - this, - 'enrollmentTime', - enrollmentTime); - fireauth.object.setReadonlyProperty( - this, - 'factorId', - factorId); - } else { - throw new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Internal assert: invalid MultiFactorInfo object'); - } -}; - - -/** @return {!Object} The plain object representation. */ -fireauth.MultiFactorInfo.prototype.toPlainObject = function() { - return { - 'uid': this['uid'], - 'displayName': this['displayName'], - 'factorId': this['factorId'], - 'enrollmentTime': this['enrollmentTime'] - }; -}; - - -/** - * Returns the factor ID based on the server response. This function needs to be - * implemented by the subclass. - * @param {!Object} resp The server response. - * @return {?fireauth.constants.SecondFactorType} The factor ID based on the - * response type. - * @protected - */ -fireauth.MultiFactorInfo.prototype.getFactorId = goog.abstractMethod; - - -/** - * Returns the corresponding `firebase.auth.MultiFactor` instance if the - * server response maps to one. Otherwise, null is returned. - * @param {?Object} resp The server response. - * @return {?fireauth.MultiFactorInfo} The corresponding - * `firebase.auth.MultiFactorInfo` instance, null otherwise. - */ -fireauth.MultiFactorInfo.fromServerResponse = function(resp) { - var multiFactorInfo; - // Only PhoneMultiFactorInfo currently available. - try { - multiFactorInfo = new fireauth.PhoneMultiFactorInfo(resp); - } catch (e) { - multiFactorInfo = null; - } - return multiFactorInfo; -}; - - -/** - * Returns the corresponding `firebase.auth.MultiFactor` instance if the - * plain object maps to one. Otherwise, null is returned. - * @param {?Object} obj The plain object representation. - * @return {?fireauth.MultiFactorInfo} The corresponding - * `firebase.auth.MultiFactorInfo` instance, null otherwise. - */ -fireauth.MultiFactorInfo.fromPlainObject = function(obj) { - var multiFactorInfo = null; - var resp = {}; - if (!obj) { - return null; - } - if (obj['uid']) { - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.MFA_ENROLLMENT_ID] = - obj['uid']; - } - if (obj['displayName']) { - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.DISPLAY_NAME] = - obj['displayName']; - } - if (obj['enrollmentTime']) { - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.ENROLLED_AT] = - new Date(obj['enrollmentTime']).toISOString(); - } - if (obj['phoneNumber']) { - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.PHONE_INFO] = - obj['phoneNumber']; - } - - // Only PhoneMultiFactorInfo currently available. - try { - multiFactorInfo = new fireauth.PhoneMultiFactorInfo(resp); - } catch (e) { - // Ignore error. - } - return multiFactorInfo; -}; - - -/** - * MfaEnrollment server side response fields. - * @enum {string} - */ -fireauth.MultiFactorInfo.MfaEnrollmentField = { - DISPLAY_NAME: 'displayName', - ENROLLED_AT: 'enrolledAt', - MFA_ENROLLMENT_ID: 'mfaEnrollmentId', - PHONE_INFO: 'phoneInfo' -}; - - -/** - * Initializes a `firebase.auth.PhoneMultiFactorInfo` instance from the provided - * server response. - * @param {?Object} resp The server response. - * @constructor - * @extends {fireauth.MultiFactorInfo} - */ -fireauth.PhoneMultiFactorInfo = function(resp) { - fireauth.PhoneMultiFactorInfo.base(this, 'constructor', resp); - fireauth.object.setReadonlyProperty( - this, - 'phoneNumber', - // PhoneInfo may be masked for security reasons for sign-in flows after - // the user signs in with the first factor but hasn't yet proven ownership - // of the second factor yet. - // For enrollment flows or for a user already signed in with a second - // factor, this field should not be masked. - resp[fireauth.MultiFactorInfo.MfaEnrollmentField.PHONE_INFO]); -}; -goog.inherits( - fireauth.PhoneMultiFactorInfo, fireauth.MultiFactorInfo); - - -/** - * Implements the factor ID getter based on the response. If the response is an - * invalid PhoneMultiFactorInfo, null is returned. - * @param {!Object} resp The server response. - * @return {?fireauth.constants.SecondFactorType} The phone factor ID. - * @protected - * @override - */ -fireauth.PhoneMultiFactorInfo.prototype.getFactorId = function(resp) { - return !!resp[fireauth.MultiFactorInfo.MfaEnrollmentField.PHONE_INFO] ? - fireauth.constants.SecondFactorType.PHONE : null; -}; - - -/** - * @return {!Object} The plain object representation. - * @override - */ -fireauth.PhoneMultiFactorInfo.prototype.toPlainObject = function() { - var obj = fireauth.PhoneMultiFactorInfo.base(this, 'toPlainObject'); - obj['phoneNumber'] = this['phoneNumber']; - return obj; -}; diff --git a/packages/auth/src/multifactorresolver.js b/packages/auth/src/multifactorresolver.js deleted file mode 100644 index f8a4a72c6a3..00000000000 --- a/packages/auth/src/multifactorresolver.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the multi-factor resolver class used to facilitate - * recovery when a multi-factor user tries to sign-in with a first factor. - */ - -goog.provide('fireauth.MultiFactorResolver'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.object'); -goog.require('goog.array'); -goog.require('goog.object'); - - -/** - * Initializes a `MultiFactorResolver` instance. This is used when a - * multi-factor user signs in with the first factor but is required to provide - * a second factor assertion before completing sign-in. - * - * @param {!fireauth.Auth} auth The Auth instance. - * @param {!fireauth.MultiFactorResolver.ErrorResponse} errorResponse The server - * error response containing the pending multi-factor credential. - * @param {function({idToken: string, refreshToken: string}): - * !goog.Promise} onIdTokenResolver - * A function that takes the assertion token response and any previous - * information returned with the error and completes sign in with a - * `UserCredential`. - * @constructor - */ -fireauth.MultiFactorResolver = function( - auth, errorResponse, onIdTokenResolver) { - var pendingCredential = errorResponse && errorResponse[ - fireauth.MultiFactorResolver.SignInResponseField.MFA_PENDING_CREDENTIAL]; - if (!pendingCredential) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'Internal assert: Invalid MultiFactorResolver'); - } - /** @const @private {!fireauth.Auth} The Auth instance. */ - this.auth_ = auth; - /** - * @const @private {!fireauth.MultiFactorResolver.ErrorResponse} The server - * error response with the pending credential. - */ - this.errorResponse_ = goog.object.clone(errorResponse); - /** - * @const @private {function({idToken: string, refreshToken: string}): - * !goog.Promise} The ID - * token resolver. - */ - this.onIdTokenResolver_ = onIdTokenResolver; - /** - * @const @private {!fireauth.MultiFactorSession} The corresponding - * multi-factor session. - */ - this.session_ = new fireauth.MultiFactorSession( - null, - pendingCredential); - /** - * @const @private {!Array} The list of - * multi-factor hints corresponding to the current user session. - */ - this.hints_ = []; - var enrollmentList = errorResponse[ - fireauth.MultiFactorResolver.SignInResponseField.MFA_INFO] || []; - var self = this; - goog.array.forEach(enrollmentList, function(mfaEnrollment) { - var info = fireauth.MultiFactorInfo.fromServerResponse(mfaEnrollment); - if (info) { - self.hints_.push(info); - } - }); - fireauth.object.setReadonlyProperty(this, 'auth', this.auth_); - fireauth.object.setReadonlyProperty(this, 'session', this.session_); - fireauth.object.setReadonlyProperty( - this, 'hints', this.hints_); -}; - - -/** - * The server side error response on multi-factor sign-in. - * @typedef {{ - * mfaInfo: (?Array|undefined), - * mfaPendingCredential: (?string|undefined) - * }} - */ -fireauth.MultiFactorResolver.ErrorResponse; - -/** - * Sign in response fields for multi-factor sign-in. - * @enum {string} - */ -fireauth.MultiFactorResolver.SignInResponseField = { - MFA_INFO: 'mfaInfo', - MFA_PENDING_CREDENTIAL: 'mfaPendingCredential' -}; - - -/** - * Completes the second factor sign-in with the multi-factor assertion provided - * and returns a promise that resolves with the `UserCredential` object. - * - * @param {!fireauth.MultiFactorAssertion} assertion The multi-factor assertion - * to resolve sign-in with. - * @return {!goog.Promise} A promise that - * resolves with the `UserCredential` after ID token processing. - */ -fireauth.MultiFactorResolver.prototype.resolveSignIn = function(assertion) { - var self = this; - return assertion.process(this.auth_.getRpcHandler(), this.session_) - .then(function(result) { - var newSignInResponse = goog.object.clone(self.errorResponse_); - // These fields are no longer needed. - delete newSignInResponse[ - fireauth.MultiFactorResolver.SignInResponseField.MFA_INFO]; - delete newSignInResponse[fireauth.MultiFactorResolver - .SignInResponseField.MFA_PENDING_CREDENTIAL]; - goog.object.extend(newSignInResponse, result); - // Return ID token/refresh token result and the original error response. - // This is needed as the original server response may contain additional - // data such as OAuth credentials, raw user info, etc that needs to be - // returned to the developer on successful sign-in. - return self.onIdTokenResolver_( - /** @type {{idToken: string, refreshToken: string}} */ ( - newSignInResponse)); - }); -}; diff --git a/packages/auth/src/multifactorsession.js b/packages/auth/src/multifactorsession.js deleted file mode 100644 index f7cdba22868..00000000000 --- a/packages/auth/src/multifactorsession.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the multi-factor session object used for enrolling a - * second factor on a user or helping sign in an enrolled user with a second - * factor. - */ - -goog.provide('fireauth.MultiFactorSession'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('goog.Promise'); - - -/** - * Initializes an instance of a multi-factor session object used for enrolling - * a second factor on a user or helping sign in an enrolled user with a second - * factor. - * This will be constructed either after calling `user.getIdToken()` or from the - * error containing the pending MFA credential after sign-in. - * @param {?string} idToken The ID token for an enroll flow. This has to be - * retrieved after recent authentication. - * @param {?string=} mfaPendingCredential The pending credential after an - * enrolled second factor user signs in successfully with the first factor. - * @constructor - */ -fireauth.MultiFactorSession = function(idToken, mfaPendingCredential) { - if (!idToken && !mfaPendingCredential) { - throw new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Internal assert: no raw session string available'); - } - // Both fields should never be passed at the same time. - if (idToken && mfaPendingCredential) { - throw new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Internal assert: unable to determine the session type'); - } - /** @const @private {?string} The ID token for an enroll flow. */ - this.idToken_ = idToken || null; - /** @const @private {?string} The pending credential for a sign-in flow. */ - this.mfaPendingCredential_ = mfaPendingCredential || null; - /** @const @public {!fireauth.MultiFactorSession.Type} The session type.*/ - this.type = this.idToken_ ? - fireauth.MultiFactorSession.Type.ENROLL : - fireauth.MultiFactorSession.Type.SIGN_IN; -}; - - -/** - * Enum representing the type of multi-factor session. - * @enum {string} - */ -fireauth.MultiFactorSession.Type = { - ENROLL: 'enroll', - SIGN_IN: 'signin' -}; - - -/** - * Returns a promise that resolves with the raw session string. - * @return {!goog.Promise} A promise that resolves with the raw session - * string. - */ -fireauth.MultiFactorSession.prototype.getRawSession = function() { - return this.idToken_ ? - goog.Promise.resolve(this.idToken_) : - goog.Promise.resolve(this.mfaPendingCredential_); -}; - - -/** - * Returns the plain object representation of the session object. - * @return {!Object} The plain object representation of the session object. - */ -fireauth.MultiFactorSession.prototype.toPlainObject = function() { - if (this.type == fireauth.MultiFactorSession.Type.ENROLL) { - return { - 'multiFactorSession': { - 'idToken': this.idToken_ - } - }; - } else { - return { - 'multiFactorSession': { - 'pendingCredential': this.mfaPendingCredential_ - } - }; - } -}; - - -/** - * Converts a plain object to a `fireauth.MultiFactorSession` if applicable. - * @param {?Object} obj The plain object to convert to a - * `fireauth.MultiFactorSession`. - * @return {?fireauth.MultiFactorSession} The corresponding - * `fireauth.MultiFactorSession` representation, null otherwise. - */ -fireauth.MultiFactorSession.fromPlainObject = function(obj) { - if (obj && obj['multiFactorSession']) { - if (obj['multiFactorSession']['pendingCredential']) { - return new fireauth.MultiFactorSession( - null, obj['multiFactorSession']['pendingCredential']); - } else if (obj['multiFactorSession']['idToken']) { - return new fireauth.MultiFactorSession( - obj['multiFactorSession']['idToken'], null); - } - } - return null; -}; diff --git a/packages/auth/src/multifactoruser.js b/packages/auth/src/multifactoruser.js deleted file mode 100644 index ae11740039b..00000000000 --- a/packages/auth/src/multifactoruser.js +++ /dev/null @@ -1,240 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the `MultiFactorUser` class used to retrieve the - * enrolled second factors on a user and to facilitate enrollment and - * unenrollment of second factors. - */ - -goog.provide('fireauth.MultiFactorUser'); - -goog.require('fireauth.MultiFactorAssertion'); -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.UserEventType'); -goog.require('fireauth.object'); -goog.require('goog.array'); -goog.require('goog.events'); - - -/** - * Initializes the multi-factor instance corresponding to a signed in user. - * This provides the ability to retrieve the enrolled second factors for that - * user, as well as the ability to enroll new second factors or unenroll - * existing ones. - * @param {!fireauth.AuthUser} user The user the multi-factor instance - * represents. - * @param {?fireauth.AuthUser.AccountInfo=} accountInfo The optional user - * account info. - * @constructor - */ -fireauth.MultiFactorUser = function(user, accountInfo) { - /** - * @private {!fireauth.AuthUser} The user this multi-factor instance - * represents. - */ - this.user_ = user; - /** @private {!Array} The enrolled factors. */ - this.enrolledFactors_ = []; - /** - * @const @private {function({userServerResponse: !Object})} The handler for - * user reload events. - */ - this.userReloadedListener_ = goog.bind(this.handleUserReload_, this); - goog.events.listen( - this.user_, - fireauth.UserEventType.USER_RELOADED, - this.userReloadedListener_); - var enrolledFactors = []; - // AccountInfo is typically loaded from storage where it is stored in plain - // object format. Otherwise, the enrolled factors will be loaded from - // getAccountInfo response triggered by user reload event. - if (accountInfo && - accountInfo['multiFactor'] && - accountInfo['multiFactor']['enrolledFactors']) { - var enrolledFactorsPlainObject = - accountInfo['multiFactor']['enrolledFactors']; - goog.array.forEach(enrolledFactorsPlainObject, function(mfaEnrollment) { - var info = fireauth.MultiFactorInfo.fromPlainObject(mfaEnrollment); - if (info) { - enrolledFactors.push(info); - } - }); - } - this.updateEnrolledFactors_(enrolledFactors); -}; - - -/** - * @const @private {string} The key for the list of second factor enrollments in - * the GetAccountInfo server response. - */ -fireauth.MultiFactorUser.GET_ACCOUNT_INFO_MFA_INFO_ = 'mfaInfo'; - - -/** @return {!fireauth.AuthUser} The corresponding user. */ -fireauth.MultiFactorUser.prototype.getUser = function() { - return this.user_; -}; - - -/** - * Extracts the enrolled factors from getAccountInfo response and returns an - * array of corresponding multi-factor info data. - * @param {!Object} resp The GetAccountInfo response object. - * @return {!Array} The enrolled factors. - * @private - */ -fireauth.MultiFactorUser.extractEnrolledFactors_ = function(resp) { - // Parse MFA enrollments. - var mfaInfo = resp[fireauth.MultiFactorUser.GET_ACCOUNT_INFO_MFA_INFO_] || []; - var enrolledFactors = []; - goog.array.forEach(mfaInfo, function(mfaEnrollment) { - var info = fireauth.MultiFactorInfo.fromServerResponse(mfaEnrollment); - if (info) { - enrolledFactors.push(info); - } - }); - return enrolledFactors; -}; - - -/** - * Handles user reload event. This will parse the enrollments from the - * response and update them on the current multi-factor instance. - * @param {{userServerResponse: !Object}} event The user reload event. - * @private - */ -fireauth.MultiFactorUser.prototype.handleUserReload_ = function(event) { - this.updateEnrolledFactors_(fireauth.MultiFactorUser.extractEnrolledFactors_( - event.userServerResponse)); -}; - - -/** - * Updates the enrolledFactors property. - * @param {!Array} enrolledFactors The new list of - * `MultiFactorInfo` objects on the user. - * @private - */ -fireauth.MultiFactorUser.prototype.updateEnrolledFactors_ = - function(enrolledFactors) { - this.enrolledFactors_ = enrolledFactors; - fireauth.object.setReadonlyProperty( - this, 'enrolledFactors', enrolledFactors); -}; - - -/** - * Copies the list of enrolled factors on the user. This facilitates copying a - * user to another user. The underlying user reference is not updated. - * @param {!fireauth.MultiFactorUser} multiFactorUser The instance to copy. - */ -fireauth.MultiFactorUser.prototype.copy = function(multiFactorUser) { - this.updateEnrolledFactors_(multiFactorUser.enrolledFactors_); -}; - - -/** - * Provides a multi-factor session used to start a multi-factor enrollment flow. - * @return {!goog.Promise} A promise that resolves - * with a multi-factor session. - */ -fireauth.MultiFactorUser.prototype.getSession = function() { - return this.user_.getIdToken() - .then(function(idToken) { - return new fireauth.MultiFactorSession(idToken, null); - }); -}; - - -/** - * Enrolls a second factor as identified by the multi-factor assertion for - * the current user. - * @param {!fireauth.MultiFactorAssertion} assertion The multi-factor assertion. - * @param {?string=} displayName The optional display name used to identify - * the 2nd factor to the end user. - * @return {!goog.Promise} A promise that resolves when the second factor - * is enrolled. - */ -fireauth.MultiFactorUser.prototype.enroll = function(assertion, displayName) { - var self = this; - var rpcHandler = this.user_.getRpcHandler(); - return this.getSession().then(function(session) { - return assertion.process(rpcHandler, session, displayName); - }).then(function(tokenResponse) { - // New tokens will be issued after enrollment of the new second factors. - // They need to be updated on the user. - self.user_.updateTokensIfPresent(tokenResponse); - // The user needs to be reloaded to get the new multi-factor information - // from server. USER_RELOADED event will be triggered and `enrolledFactors` - // will be updated. - return self.user_.reload(); - }); -}; - - -/** - * Removes a second factor from the current user. The factor to be removed can - * either be identified with the corresponding MultiFactorInfo object or with - * the second factor's uid string. - * - * As part of the unenrollment process, the backend may decide to log the user - * out. If so, this will still succeed and invalidate the user's state. - * @param {!fireauth.MultiFactorInfo|string} target The second factor to remove. - * @return {!goog.Promise} - */ -fireauth.MultiFactorUser.prototype.unenroll = function(target) { - var self = this; - var uid = typeof target === 'string' ? target : target['uid']; - var rpcHandler = this.user_.getRpcHandler(); - return this.user_.getIdToken().then(function(idToken) { - return rpcHandler.withdrawMfa(idToken, uid); - }).then(function(tokenResponse) { - // Remove the second factor from the user's list. - var enrolledFactors = goog.array.filter(self.enrolledFactors_, - function(info) { - return info['uid'] != uid; - }); - self.updateEnrolledFactors_(enrolledFactors); - // Depending on whether the backend decided to revoke the user's session, - // the tokenResponse may be empty. If the tokens were not updated (and they - // are now invalid), reloading the user will discover this and invalidate - // the user's state accordingly. - self.user_.updateTokensIfPresent(tokenResponse); - return self.user_.reload().thenCatch(function(error) { - if (error['code'] != 'auth/user-token-expired') { - throw error; - } - }); - }); -}; - - -/** - * @return {!Object} The plain object representation of the `MultiFactorUser`. - */ -fireauth.MultiFactorUser.prototype.toPlainObject = function() { - return { - 'multiFactor': { - 'enrolledFactors': goog.array.map(this.enrolledFactors_, function(info) { - return info.toPlainObject(); - }) - } - }; -}; diff --git a/packages/auth/src/oauthhelperstate.js b/packages/auth/src/oauthhelperstate.js deleted file mode 100644 index 8d33cac12ce..00000000000 --- a/packages/auth/src/oauthhelperstate.js +++ /dev/null @@ -1,212 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the OAuth helper widget state. - */ - -goog.provide('fireauth.OAuthHelperState'); - -goog.require('fireauth.AuthEvent'); - - -/** - * Defines the OAuth helper widget state. - * @param {string} apiKey The API key. - * @param {string} appName The App name. - * @param {!fireauth.AuthEvent.Type} type The OAuth helper mode - * @param {?string=} opt_eventId The event identifier. - * @param {?string=} opt_redirectUrl The optional redirect URL for redirect - * mode. - * @param {?string=} opt_clientVersion The optional client version. - * @param {?string=} opt_displayName The application display name. - * @param {?string=} opt_apn The optional Android package name. - * @param {?string=} opt_ibi The optional iOS bundle ID. - * @param {?string=} opt_eid The optional Auth endpoint ID. - * @param {?Array=} opt_frameworks The optional list of framework IDs. - * @param {?string=} opt_clientId The optional OAuth client ID. - * @param {?string=} opt_sha1Cert The optional SHA-1 hash of Android cert. - * @param {?string=} opt_tenantId The optional tenant ID. - * @constructor - */ -fireauth.OAuthHelperState = function( - apiKey, appName, type, opt_eventId, opt_redirectUrl, opt_clientVersion, - opt_displayName, opt_apn, opt_ibi, opt_eid, opt_frameworks, opt_clientId, - opt_sha1Cert, opt_tenantId) { - /** @const @private {string} The API key. */ - this.apiKey_ = apiKey; - /** @const @private {string} The App name. */ - this.appName_ = appName; - /** @const @private {!fireauth.AuthEvent.Type} The OAuth helper mode. */ - this.type_ = type; - /** @const @private {?string} The event identifier. */ - this.eventId_ = opt_eventId || null; - /** @const @private {?string} The redirect URL for redirect mode. */ - this.redirectUrl_ = opt_redirectUrl || null; - /** @const @private {?string} The client version. */ - this.clientVersion_ = opt_clientVersion || null; - /** @const @private {?string} The application display name. */ - this.displayName_ = opt_displayName || null; - /** @const @private {?string} The Android package name. */ - this.apn_ = opt_apn || null; - /** @const @private {?string} The iOS bundle ID. */ - this.ibi_ = opt_ibi || null; - /** @const @private {?string} The endpoint ID. */ - this.eid_ = opt_eid || null; - /** @const @private {!Array} The list of framework IDs. */ - this.frameworks_ = opt_frameworks || []; - /** @const @private {?string} The OAuth client ID. */ - this.clientId_ = opt_clientId || null; - /** @const @private {?string} The SHA-1 hash of Android cert. */ - this.sha1Cert_ = opt_sha1Cert || null; - /** @const @private {?string} The tenant ID. */ - this.tenantId_ = opt_tenantId || null; -}; - - -/** @return {?string} The OAuth client ID. */ -fireauth.OAuthHelperState.prototype.getClientId = function() { - return this.clientId_; -}; - - -/** @return {?string} The SHA-1 hash of the Android cert. */ -fireauth.OAuthHelperState.prototype.getSha1Cert = function() { - return this.sha1Cert_; -}; - - -/** @return {!fireauth.AuthEvent.Type} The type of Auth event. */ -fireauth.OAuthHelperState.prototype.getType = function() { - return this.type_; -}; - - -/** @return {?string} The Auth event identifier. */ -fireauth.OAuthHelperState.prototype.getEventId = function() { - return this.eventId_; -}; - - -/** @return {string} The API key. */ -fireauth.OAuthHelperState.prototype.getApiKey = function() { - return this.apiKey_; -}; - - -/** @return {string} The App name. */ -fireauth.OAuthHelperState.prototype.getAppName = function() { - return this.appName_; -}; - - -/** @return {?string} The redirect URL. */ -fireauth.OAuthHelperState.prototype.getRedirectUrl = function() { - return this.redirectUrl_; -}; - - -/** @return {?string} The client version. */ -fireauth.OAuthHelperState.prototype.getClientVersion = function() { - return this.clientVersion_; -}; - - -/** @return {?string} The application display name if available. */ -fireauth.OAuthHelperState.prototype.getDisplayName = function() { - return this.displayName_; -}; - - -/** @return {?string} The Android package name. */ -fireauth.OAuthHelperState.prototype.getApn = function() { - return this.apn_; -}; - - -/** @return {?string} The iOS bundle ID. */ -fireauth.OAuthHelperState.prototype.getIbi = function() { - return this.ibi_; -}; - - -/** @return {?string} The Auth endpoint ID. */ -fireauth.OAuthHelperState.prototype.getEndpointId = function() { - return this.eid_; -}; - - -/** @return {?string} The tenant ID. */ -fireauth.OAuthHelperState.prototype.getTenantId = function() { - return this.tenantId_; -}; - - -/** @return {!Array} The list of framework IDs. */ -fireauth.OAuthHelperState.prototype.getFrameworks = function() { - return this.frameworks_; -}; - - -/** @return {!Object} The plain object representation of OAuth helper state. */ -fireauth.OAuthHelperState.prototype.toPlainObject = function() { - return { - 'apiKey': this.apiKey_, - 'appName': this.appName_, - 'type': this.type_, - 'eventId': this.eventId_, - 'redirectUrl': this.redirectUrl_, - 'clientVersion': this.clientVersion_, - 'displayName': this.displayName_, - 'apn': this.apn_, - 'ibi': this.ibi_, - 'eid': this.eid_, - 'fw': this.frameworks_, - 'clientId': this.clientId_, - 'sha1Cert': this.sha1Cert_, - 'tenantId': this.tenantId_ - }; -}; - - -/** - * @param {?Object} rawResponse The plain object representation of OAuth helper - * state. - * @return {?fireauth.OAuthHelperState} The OAuth helper state representation of - * plain object. - */ -fireauth.OAuthHelperState.fromPlainObject = function(rawResponse) { - var response = rawResponse || {}; - if (response['type'] && response['apiKey']) { - return new fireauth.OAuthHelperState( - response['apiKey'], - response['appName'] || '', - response['type'], - response['eventId'], - response['redirectUrl'], - response['clientVersion'], - response['displayName'], - response['apn'], - response['ibi'], - response['eid'], - response['fw'], - response['clientId'], - response['sha1Cert'], - response['tenantId']); - } - return null; -}; diff --git a/packages/auth/src/oauthsigninhandler.js b/packages/auth/src/oauthsigninhandler.js deleted file mode 100644 index 32d2f959095..00000000000 --- a/packages/auth/src/oauthsigninhandler.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the OAuthSignInHandler interface used to start OAuth - * sign-in flows via popup and redirect and detect incoming OAuth responses. - */ - -goog.provide('fireauth.OAuthSignInHandler'); - -/** - * The interface that represents an OAuth sign-in handler. - * @interface - */ -fireauth.OAuthSignInHandler = function() {}; - - -/** - * @return {boolean} Whether the handler should be initialized early. - */ -fireauth.OAuthSignInHandler.prototype.shouldBeInitializedEarly = function() {}; - - -/** - * @return {boolean} Whether the sign-in handler in the current environment - * has volatile session storage. - */ -fireauth.OAuthSignInHandler.prototype.hasVolatileStorage = function() {}; - - -/** - * @return {!goog.Promise} The promise that resolves when the handler is - * initialized and ready. - */ -fireauth.OAuthSignInHandler.prototype.initializeAndWait = function() {}; - - -/** - * Processes the OAuth popup request. The popup instance must be provided - * externally and on error, the requestor must close the window. - * @param {?Window} popupWin The popup window reference. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {function()} onInitialize The function to call on initialization. - * @param {function(*)} onError The function to call on error. - * @param {string=} opt_eventId The optional event ID. - * @param {boolean=} opt_alreadyRedirected Whether popup is already redirected - * to final destination. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} The popup window promise. - */ -fireauth.OAuthSignInHandler.prototype.processPopup = - function(popupWin, mode, provider, onInitialize, onError, opt_eventId, - opt_alreadyRedirected, opt_tenantId) {}; - - -/** - * Processes the OAuth redirect request. - * @param {!fireauth.AuthEvent.Type} mode The Auth event type. - * @param {!fireauth.AuthProvider} provider The Auth provider to sign in with. - * @param {?string=} opt_eventId The optional event ID. - * @param {?string=} opt_tenantId The optional tenant ID. - * @return {!goog.Promise} - */ -fireauth.OAuthSignInHandler.prototype.processRedirect = - function(mode, provider, opt_eventId, opt_tenantId) {}; - - -/** - * @return {boolean} Whether the handler will unload the current page on - * redirect operations. - */ -fireauth.OAuthSignInHandler.prototype.unloadsOnRedirect = function() {}; - - -/** - * Waits for popup window to close. When closed start timeout listener for popup - * pending promise. If in the process, an error is detected, the error is - * funnelled back and the popup is closed. - * @param {!Window} popupWin The popup window. - * @param {!function(!fireauth.AuthError)} onError The on error callback. - * @param {number} timeoutDuration The time to wait in ms after the popup is - * closed before triggering the popup closed by user error. - * @return {!goog.Promise} - */ -fireauth.OAuthSignInHandler.prototype.startPopupTimeout = - function(popupWin, onError, timeoutDuration) {}; - - -/** - * @param {!function(?fireauth.AuthEvent):boolean} listener The Auth event - * listener to add. - */ -fireauth.OAuthSignInHandler.prototype.addAuthEventListener = - function(listener) {}; - - -/** - * @param {!function(?fireauth.AuthEvent):boolean} listener The Auth event - * listener to remove. - */ -fireauth.OAuthSignInHandler.prototype.removeAuthEventListener = - function(listener) {}; diff --git a/packages/auth/src/object.js b/packages/auth/src/object.js deleted file mode 100644 index f71e06b8b4b..00000000000 --- a/packages/auth/src/object.js +++ /dev/null @@ -1,215 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Provides methods for manipulating objects. - */ - -goog.provide('fireauth.object'); - -goog.require('fireauth.deprecation'); -goog.require('fireauth.deprecation.Deprecations'); - - -/** - * Checks whether the defineProperty method allows to change the value of - * the property. - * @return {boolean} Whether the defineProperty method allows to change the - * value of the property. - * @private - */ -fireauth.object.isReadonlyConfigurable_ = function() { - // Android 2.3 stock browser doesn't allow to change the value of - // a read-only property once defined. - try { - var obj = {}; - Object.defineProperty(obj, 'abcd', { - configurable: true, - enumerable: true, - value: 1 - }); - Object.defineProperty(obj, 'abcd', { - configurable: true, - enumerable: true, - value: 2 - }); - return obj['abcd'] == 2; - } catch (e) { - return false; - } -}; - - -/** - * @private {boolean} Whether the defineProperty method allows to change the - * value of the property. - */ -fireauth.object.readonlyConfigurable_ = - fireauth.object.isReadonlyConfigurable_(); - - -/** - * Defines a property on an object that is not writable by clients. However, the - * property can be overwritten within the Firebase library through subsequent - * calls to setReadonlyProperty. - * - * In browsers that do not support read-only properties (notably IE8 and below), - * fall back to writable properties. - * - * @param {!Object} obj The object to which we add the property. - * @param {string} key The name of the property. - * @param {*} value The desired value. - */ -fireauth.object.setReadonlyProperty = function(obj, key, value) { - if (fireauth.object.readonlyConfigurable_) { - Object.defineProperty(obj, key, { - configurable: true, - enumerable: true, - value: value - }); - } else { - obj[key] = value; - } -}; - - -/** - * Defines a deprecated property, which emits a warning if the developer tries - * to use it. - * - * In browsers that do not support getters, we fall back to a normal property - * with no message. - * - * @param {!Object} obj The object to which we add the property. - * @param {string} key The name of the deprecated property. - * @param {*} value The desired value. - * @param {!fireauth.deprecation.Deprecations} deprecationMessage The - * deprecation warning to display. - */ -fireauth.object.setDeprecatedReadonlyProperty = function(obj, key, value, - deprecationMessage) { - if (fireauth.object.readonlyConfigurable_) { - Object.defineProperty(obj, key, { - configurable: true, - enumerable: true, - get: function() { - fireauth.deprecation.log(deprecationMessage); - return value; - } - }); - } else { - obj[key] = value; - } -}; - - -/** - * Defines properties on an object that are not writable by clients, equivalent - * to many calls to setReadonlyProperty. - * @param {!Object} obj The object to which we add the properties. - * @param {?Object} props An object that maps the keys and values - * that we wish to add. - */ -fireauth.object.setReadonlyProperties = function(obj, props) { - if (!props) { - return; - } - - for (var key in props) { - if (props.hasOwnProperty(key)) { - fireauth.object.setReadonlyProperty(obj, key, props[key]); - } - } -}; - - -/** - * Makes a shallow read-only copy of an object. The writability of any child - * objects will not be affected. - * @param {?Object} obj The object that we wish to copy. - * @return {!Object} - */ -fireauth.object.makeReadonlyCopy = function(obj) { - var output = {}; - fireauth.object.setReadonlyProperties(output, obj); - return output; -}; - - -/** - * Makes a shallow writable copy of a read-only object. The writability of any - * child objects will not be affected. - * @param {?Object} obj The object that we wish to copy. - * @return {!Object} - */ -fireauth.object.makeWritableCopy = function(obj) { - var output = {}; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - output[key] = obj[key]; - } - } - return output; -}; - - -/** - * Returns true if the all the specified fields are present in obj and are not - * null, undefined, or the empty string. If the field list is empty, returns - * true regardless of the value of obj. - * @param {?Object=} opt_obj The object. - * @param {?Array=} opt_fields The desired fields of the object. - * @return {boolean} True if obj has all the specified fields. - */ -fireauth.object.hasNonEmptyFields = function(opt_obj, opt_fields) { - if (!opt_fields || !opt_fields.length) { - return true; - } - if (!opt_obj) { - return false; - } - for (var i = 0; i < opt_fields.length; i++) { - var field = opt_obj[opt_fields[i]]; - if (field === undefined || field === null || field === '') { - return false; - } - } - return true; -}; - - -/** - * Traverses the specified object and creates a read-only deep copy of it. - * This will fail when circular references are contained within the object. - * @param {*} obj The object to make a read-only copy from. - * @return {*} A Read-only copy of the obj specified. - */ -fireauth.object.unsafeCreateReadOnlyCopy = function(obj) { - var copy = obj; - if (typeof obj == 'object' && obj != null) { - // Make the right type of copy. - copy = 'length' in obj ? [] : {}; - // Make a deep copy. - for (var key in obj) { - fireauth.object.setReadonlyProperty( - copy, key, fireauth.object.unsafeCreateReadOnlyCopy(obj[key])); - } - } - // Return the copy. - return copy; -}; - diff --git a/packages-exp/auth-exp/src/platform_browser/auth.test.ts b/packages/auth/src/platform_browser/auth.test.ts similarity index 99% rename from packages-exp/auth-exp/src/platform_browser/auth.test.ts rename to packages/auth/src/platform_browser/auth.test.ts index 4492e13bd9a..3b33f357db2 100644 --- a/packages-exp/auth-exp/src/platform_browser/auth.test.ts +++ b/packages/auth/src/platform_browser/auth.test.ts @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { Auth, Persistence, diff --git a/packages-exp/auth-exp/src/platform_browser/auth_window.ts b/packages/auth/src/platform_browser/auth_window.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/auth_window.ts rename to packages/auth/src/platform_browser/auth_window.ts diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.iframes.ts b/packages/auth/src/platform_browser/iframe/gapi.iframes.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/iframe/gapi.iframes.ts rename to packages/auth/src/platform_browser/iframe/gapi.iframes.ts diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.test.ts b/packages/auth/src/platform_browser/iframe/gapi.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/iframe/gapi.test.ts rename to packages/auth/src/platform_browser/iframe/gapi.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.ts b/packages/auth/src/platform_browser/iframe/gapi.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/iframe/gapi.ts rename to packages/auth/src/platform_browser/iframe/gapi.ts diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/iframe.test.ts b/packages/auth/src/platform_browser/iframe/iframe.test.ts similarity index 98% rename from packages-exp/auth-exp/src/platform_browser/iframe/iframe.test.ts rename to packages/auth/src/platform_browser/iframe/iframe.test.ts index 60a2d296406..e5198e206ca 100644 --- a/packages-exp/auth-exp/src/platform_browser/iframe/iframe.test.ts +++ b/packages/auth/src/platform_browser/iframe/iframe.test.ts @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; import { FirebaseError } from '@firebase/util'; import { diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/iframe.ts b/packages/auth/src/platform_browser/iframe/iframe.ts similarity index 98% rename from packages-exp/auth-exp/src/platform_browser/iframe/iframe.ts rename to packages/auth/src/platform_browser/iframe/iframe.ts index 9e634519a81..e32615c7ece 100644 --- a/packages-exp/auth-exp/src/platform_browser/iframe/iframe.ts +++ b/packages/auth/src/platform_browser/iframe/iframe.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; import { querystring } from '@firebase/util'; import { DefaultConfig } from '../../../internal'; diff --git a/packages-exp/auth-exp/src/platform_browser/index.ts b/packages/auth/src/platform_browser/index.ts similarity index 96% rename from packages-exp/auth-exp/src/platform_browser/index.ts rename to packages/auth/src/platform_browser/index.ts index a0965ce1e8c..3d206c3706d 100644 --- a/packages-exp/auth-exp/src/platform_browser/index.ts +++ b/packages/auth/src/platform_browser/index.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; import { initializeAuth } from '..'; import { registerAuth } from '../core/auth/register'; @@ -35,7 +35,7 @@ import { Auth } from '../model/public_types'; * @public */ export function getAuth(app: FirebaseApp = getApp()): Auth { - const provider = _getProvider(app, 'auth-exp'); + const provider = _getProvider(app, 'auth'); if (provider.isInitialized()) { return provider.getImmediate(); diff --git a/packages-exp/auth-exp/src/platform_browser/load_js.test.ts b/packages/auth/src/platform_browser/load_js.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/load_js.test.ts rename to packages/auth/src/platform_browser/load_js.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/load_js.ts b/packages/auth/src/platform_browser/load_js.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/load_js.ts rename to packages/auth/src/platform_browser/load_js.ts diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/index.ts b/packages/auth/src/platform_browser/messagechannel/index.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/messagechannel/index.ts rename to packages/auth/src/platform_browser/messagechannel/index.ts diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/promise.test.ts b/packages/auth/src/platform_browser/messagechannel/promise.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/messagechannel/promise.test.ts rename to packages/auth/src/platform_browser/messagechannel/promise.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/promise.ts b/packages/auth/src/platform_browser/messagechannel/promise.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/messagechannel/promise.ts rename to packages/auth/src/platform_browser/messagechannel/promise.ts diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.test.ts b/packages/auth/src/platform_browser/messagechannel/receiver.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.test.ts rename to packages/auth/src/platform_browser/messagechannel/receiver.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.ts b/packages/auth/src/platform_browser/messagechannel/receiver.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.ts rename to packages/auth/src/platform_browser/messagechannel/receiver.ts diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/sender.test.ts b/packages/auth/src/platform_browser/messagechannel/sender.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/messagechannel/sender.test.ts rename to packages/auth/src/platform_browser/messagechannel/sender.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/sender.ts b/packages/auth/src/platform_browser/messagechannel/sender.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/messagechannel/sender.ts rename to packages/auth/src/platform_browser/messagechannel/sender.ts diff --git a/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.test.ts b/packages/auth/src/platform_browser/mfa/assertions/phone.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.test.ts rename to packages/auth/src/platform_browser/mfa/assertions/phone.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.ts b/packages/auth/src/platform_browser/mfa/assertions/phone.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.ts rename to packages/auth/src/platform_browser/mfa/assertions/phone.ts diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/browser.ts b/packages/auth/src/platform_browser/persistence/browser.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/persistence/browser.ts rename to packages/auth/src/platform_browser/persistence/browser.ts diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.test.ts b/packages/auth/src/platform_browser/persistence/indexed_db.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.test.ts rename to packages/auth/src/platform_browser/persistence/indexed_db.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.ts b/packages/auth/src/platform_browser/persistence/indexed_db.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.ts rename to packages/auth/src/platform_browser/persistence/indexed_db.ts diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.test.ts b/packages/auth/src/platform_browser/persistence/local_storage.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/persistence/local_storage.test.ts rename to packages/auth/src/platform_browser/persistence/local_storage.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.ts b/packages/auth/src/platform_browser/persistence/local_storage.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/persistence/local_storage.ts rename to packages/auth/src/platform_browser/persistence/local_storage.ts diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.test.ts b/packages/auth/src/platform_browser/persistence/session_storage.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/persistence/session_storage.test.ts rename to packages/auth/src/platform_browser/persistence/session_storage.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.ts b/packages/auth/src/platform_browser/persistence/session_storage.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/persistence/session_storage.ts rename to packages/auth/src/platform_browser/persistence/session_storage.ts diff --git a/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts b/packages/auth/src/platform_browser/popup_redirect.test.ts similarity index 99% rename from packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts rename to packages/auth/src/platform_browser/popup_redirect.test.ts index 4f394572354..6159bf24052 100644 --- a/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts +++ b/packages/auth/src/platform_browser/popup_redirect.test.ts @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { SDK_VERSION } from '@firebase/app-exp'; +import { SDK_VERSION } from '@firebase/app'; import { Config } from '../model/public_types'; import { ProviderId } from '../model/enums'; diff --git a/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts b/packages/auth/src/platform_browser/popup_redirect.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/popup_redirect.ts rename to packages/auth/src/platform_browser/popup_redirect.ts diff --git a/packages-exp/auth-exp/src/platform_browser/providers/phone.test.ts b/packages/auth/src/platform_browser/providers/phone.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/providers/phone.test.ts rename to packages/auth/src/platform_browser/providers/phone.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/providers/phone.ts b/packages/auth/src/platform_browser/providers/phone.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/providers/phone.ts rename to packages/auth/src/platform_browser/providers/phone.ts diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha.ts rename to packages/auth/src/platform_browser/recaptcha/recaptcha.ts diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.test.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_loader.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.test.ts rename to packages/auth/src/platform_browser/recaptcha/recaptcha_loader.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_loader.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.ts rename to packages/auth/src/platform_browser/recaptcha/recaptcha_loader.ts diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.test.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_mock.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.test.ts rename to packages/auth/src/platform_browser/recaptcha/recaptcha_mock.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_mock.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.ts rename to packages/auth/src/platform_browser/recaptcha/recaptcha_mock.ts diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_verifier.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts rename to packages/auth/src/platform_browser/recaptcha/recaptcha_verifier.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.ts b/packages/auth/src/platform_browser/recaptcha/recaptcha_verifier.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.ts rename to packages/auth/src/platform_browser/recaptcha/recaptcha_verifier.ts diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/phone.test.ts b/packages/auth/src/platform_browser/strategies/phone.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/strategies/phone.test.ts rename to packages/auth/src/platform_browser/strategies/phone.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/phone.ts b/packages/auth/src/platform_browser/strategies/phone.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/strategies/phone.ts rename to packages/auth/src/platform_browser/strategies/phone.ts diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/popup.test.ts b/packages/auth/src/platform_browser/strategies/popup.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/strategies/popup.test.ts rename to packages/auth/src/platform_browser/strategies/popup.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/popup.ts b/packages/auth/src/platform_browser/strategies/popup.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/strategies/popup.ts rename to packages/auth/src/platform_browser/strategies/popup.ts diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts b/packages/auth/src/platform_browser/strategies/redirect.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts rename to packages/auth/src/platform_browser/strategies/redirect.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts b/packages/auth/src/platform_browser/strategies/redirect.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts rename to packages/auth/src/platform_browser/strategies/redirect.ts diff --git a/packages-exp/auth-exp/src/platform_browser/util/popup.test.ts b/packages/auth/src/platform_browser/util/popup.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/util/popup.test.ts rename to packages/auth/src/platform_browser/util/popup.test.ts diff --git a/packages-exp/auth-exp/src/platform_browser/util/popup.ts b/packages/auth/src/platform_browser/util/popup.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/util/popup.ts rename to packages/auth/src/platform_browser/util/popup.ts diff --git a/packages-exp/auth-exp/src/platform_browser/util/worker.ts b/packages/auth/src/platform_browser/util/worker.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_browser/util/worker.ts rename to packages/auth/src/platform_browser/util/worker.ts diff --git a/packages-exp/auth-exp/src/platform_cordova/plugins.ts b/packages/auth/src/platform_cordova/plugins.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_cordova/plugins.ts rename to packages/auth/src/platform_cordova/plugins.ts diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.test.ts b/packages/auth/src/platform_cordova/popup_redirect/events.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.test.ts rename to packages/auth/src/platform_cordova/popup_redirect/events.test.ts diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.ts b/packages/auth/src/platform_cordova/popup_redirect/events.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.ts rename to packages/auth/src/platform_cordova/popup_redirect/events.ts diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts b/packages/auth/src/platform_cordova/popup_redirect/popup_redirect.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts rename to packages/auth/src/platform_cordova/popup_redirect/popup_redirect.test.ts diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts b/packages/auth/src/platform_cordova/popup_redirect/popup_redirect.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts rename to packages/auth/src/platform_cordova/popup_redirect/popup_redirect.ts diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts b/packages/auth/src/platform_cordova/popup_redirect/utils.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts rename to packages/auth/src/platform_cordova/popup_redirect/utils.test.ts diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts b/packages/auth/src/platform_cordova/popup_redirect/utils.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts rename to packages/auth/src/platform_cordova/popup_redirect/utils.ts diff --git a/packages-exp/auth-exp/src/platform_cordova/strategies/redirect.ts b/packages/auth/src/platform_cordova/strategies/redirect.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_cordova/strategies/redirect.ts rename to packages/auth/src/platform_cordova/strategies/redirect.ts diff --git a/packages-exp/auth-exp/src/platform_node/index.ts b/packages/auth/src/platform_node/index.ts similarity index 98% rename from packages-exp/auth-exp/src/platform_node/index.ts rename to packages/auth/src/platform_node/index.ts index 9bc3bd9b8d5..dbb0239a431 100644 --- a/packages-exp/auth-exp/src/platform_node/index.ts +++ b/packages/auth/src/platform_node/index.ts @@ -18,7 +18,7 @@ import { AuthErrorCode } from '../core/errors'; import { _createError } from '../core/util/assert'; -import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; import { Auth } from '../model/public_types'; import { initializeAuth } from '..'; @@ -40,7 +40,7 @@ FetchProvider.initialize( // the version and declare the Node getAuth function) export function getAuth(app: FirebaseApp = getApp()): Auth { - const provider = _getProvider(app, 'auth-exp'); + const provider = _getProvider(app, 'auth'); if (provider.isInitialized()) { return provider.getImmediate(); diff --git a/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.test.ts b/packages/auth/src/platform_react_native/persistence/react_native.test.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_react_native/persistence/react_native.test.ts rename to packages/auth/src/platform_react_native/persistence/react_native.test.ts diff --git a/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.ts b/packages/auth/src/platform_react_native/persistence/react_native.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_react_native/persistence/react_native.ts rename to packages/auth/src/platform_react_native/persistence/react_native.ts diff --git a/packages-exp/auth-exp/src/platform_react_native/react-native.d.ts b/packages/auth/src/platform_react_native/react-native.d.ts similarity index 100% rename from packages-exp/auth-exp/src/platform_react_native/react-native.d.ts rename to packages/auth/src/platform_react_native/react-native.d.ts diff --git a/packages/auth/src/proactiverefresh.js b/packages/auth/src/proactiverefresh.js deleted file mode 100644 index 1789ec49872..00000000000 --- a/packages/auth/src/proactiverefresh.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Utility for proactive refresh with exponential backoff - * algorithm typically used to define a retry policy for certain async - * operations. - */ - -goog.provide('fireauth.ProactiveRefresh'); - -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Timer'); - - -/** - * The helper utility used to proactively refresh a certain operation based on - * certain constraints with an exponential backoff retrial policy when - * specific recoverable errors occur. Typically this is used to retry an - * operation on network errors. - * @param {!function():!goog.Promise} operation The promise returning operation - * to run. - * @param {!function(*):boolean} retryPolicy A function that takes in an error - * and returns whether a retry policy should be implemented based on the - * error. If not, the operation will not run again. - * @param {!function():number} getWaitDuration A function that returns the wait - * period before running again. - * @param {number} lowerBound The lower bound duration to wait when an error - * occurs. This is the first interval to wait before rerunning after an - * error is detected. - * @param {number} upperBound The upper bound duration to wait when an error - * keeps occurring. This upper bound should not be exceeded. - * @param {boolean=} opt_runInBackground Whether to run in the background, when - * the tab is not visible. If the refresh should only run when the app is - * visible, the operation will block until the app is visible and then run. - * @constructor @struct @final - */ -fireauth.ProactiveRefresh = function( - operation, - retryPolicy, - getWaitDuration, - lowerBound, - upperBound, - opt_runInBackground) { - /** - * @const @private {!function():!goog.Promise} The promise returning operation - * to run. - */ - this.operation_ = operation; - /** - * @const @private {!function(*):boolean} The function that takes in an error - * and returns whether a retry policy should be implemented based on the - * error caught. - */ - this.retryPolicy_ = retryPolicy; - /** - * @const @private {!function():number} The function that returns the wait - * period after a successful operation before running again. - */ - this.getWaitDuration_ = getWaitDuration; - /** - * @const @private {number} The lower bound duration to wait when an error - * first occurs. - */ - this.lowerBound_ = lowerBound; - /** - * @const @private {number} The upper bound duration to wait when an error - * keeps occurring. This upper bound should not be exceeded. - */ - this.upperBound_ = upperBound; - /** - * @const @private {boolean} Whether to run in the background, when the tab is - * not visible. - */ - this.runInBackground_ = !!opt_runInBackground; - /** - * @private {?goog.Promise} The pending promise for the next operation to run. - */ - this.pending_ = null; - /** - * @private {number} The first wait interval when a new error occurs. - */ - this.nextErrorWaitInterval_ = this.lowerBound_; - // Throw an error if the lower bound is greater than upper bound. - if (this.upperBound_ < this.lowerBound_) { - throw new Error('Proactive refresh lower bound greater than upper bound!'); - } -}; - - -/** Starts the proactive refresh based on the current configuration. */ -fireauth.ProactiveRefresh.prototype.start = function() { - // Set the next error wait interval to the lower bound. On each consecutive - // error, this will double in value until it reaches the upper bound. - this.nextErrorWaitInterval_ = this.lowerBound_; - // Start proactive refresh with clean slate (successful status). - this.process_(true); -}; - - -/** - * Returns the wait duration before the next run depending on the last run - * status. If the last operation has succeeded, returns the getWaitDuration() - * response. Otherwise, doubles the last error wait interval starting from - * lowerBound and up to upperBound. - * @param {boolean} hasSucceeded Whether last run succeeded. - * @return {number} The wait time for the next run. - * @private - */ -fireauth.ProactiveRefresh.prototype.getNextRun_ = function(hasSucceeded) { - if (hasSucceeded) { - // If last operation succeeded, reset next error wait interval and return - // the default wait duration. - this.nextErrorWaitInterval_ = this.lowerBound_; - // Return typical wait duration interval after a successful operation. - return this.getWaitDuration_(); - } else { - // Get next error wait interval. - var currentErrorWaitInterval = this.nextErrorWaitInterval_; - // Double interval for next consecutive error. - this.nextErrorWaitInterval_ *= 2; - // Make sure next wait interval does not exceed the maximum upper bound. - if (this.nextErrorWaitInterval_ > this.upperBound_) { - this.nextErrorWaitInterval_ = this.upperBound_; - } - return currentErrorWaitInterval; - } -}; - - -/** - * Processes one refresh call and sets the timer for the next call based on - * the last recorded result. - * @param {boolean} hasSucceeded Whether last run succeeded. - * @private - */ -fireauth.ProactiveRefresh.prototype.process_ = function(hasSucceeded) { - var self = this; - // Stop any other pending operation. - this.stop(); - // Wait for next scheduled run based on whether an error occurred during last - // run. - this.pending_ = goog.Timer.promise(this.getNextRun_(hasSucceeded)) - .then(function() { - // Block for conditions (if app is required to be visible) to be ready. - return self.waitUntilReady_(); - }) - .then(function() { - // Run the operation. - return self.operation_(); - }) - .then(function() { - // If successful, try again on next cycle with no previous error - // passed. - self.process_(true); - }) - .thenCatch(function(error) { - // If an error occurs, only rerun when the error meets the retry - // policy. - if (self.retryPolicy_(error)) { - // Should retry with error to trigger exponentional backoff. - self.process_(false); - } - // Any other error is considered unrecoverable. Do not try again. - }); -}; - - -/** - * Returns a promise which resolves when the current tab is visible. - * This resolves quickly if refresh is supposed to run in the background too. - * @return {!goog.Promise} The promise that resolves when the tab is visible or - * that requirement is not needed. - * @private - */ -fireauth.ProactiveRefresh.prototype.waitUntilReady_ = function() { - // Wait until app is in foreground if required. - if (this.runInBackground_) { - // If runs in background, resolve quickly. - return goog.Promise.resolve(); - } else { - // Wait for the app to be visible before resolving the promise. - return fireauth.util.onAppVisible(); - } -}; - - -/** Stops the proactive refresh from running again. */ -fireauth.ProactiveRefresh.prototype.stop = function() { - // If there is a pending promise. - if (this.pending_) { - // Cancel the pending promise and nullify it. - this.pending_.cancel(); - this.pending_ = null; - } -}; - - -/** @return {boolean} Whether the proactive refresh is running or not. */ -fireauth.ProactiveRefresh.prototype.isRunning = function() { - return !!this.pending_; -}; diff --git a/packages/auth/src/recaptchaverifier/grecaptcha.js b/packages/auth/src/recaptchaverifier/grecaptcha.js deleted file mode 100644 index 8ef306812fb..00000000000 --- a/packages/auth/src/recaptchaverifier/grecaptcha.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Defines the grecaptcha interface. - */ - -goog.provide('fireauth.grecaptcha'); - - -/** - * The reCAPTCHA interface for initializing and managing visible v2 - * reCAPTCHAs as well as invisible ones. - * @interface - */ -fireauth.grecaptcha = function() {}; - - -/** - * Creates a new instance of the recaptcha client. - * - * @param {!Element|string} elementOrId Element or element ID for the - * placeholder to render the reCAPTCHA client. - * @param {!Object} params Parameters for the recaptcha client. - * @return {number} The client ID. - */ -fireauth.grecaptcha.prototype.render = function(elementOrId, params) {}; - - -/** - * Resets a client with the given ID. If an ID is not provided, resets the - * default client. - * - * @param {number=} id The ID of the recaptcha client. - * @param {?Object=} params Parameters for the recaptcha client. - */ -fireauth.grecaptcha.prototype.reset = function(id, params) {}; - - -/** - * Gets the response for the client with the given ID. If an ID is not - * provided, gets the response for the default client. - * - * @param {number=} id The ID of the recaptcha client. - * @return {?string} - */ -fireauth.grecaptcha.prototype.getResponse = function(id) {}; - - -/** - * Programmatically triggers the invisible reCAPTCHA. - * - * @param {number=} opt_id The ID of the recaptcha client. Defaults to the - * first widget created if unspecified. - */ -fireauth.grecaptcha.prototype.execute = function(opt_id) {}; diff --git a/packages/auth/src/recaptchaverifier/grecaptchamock.js b/packages/auth/src/recaptchaverifier/grecaptchamock.js deleted file mode 100644 index 8d249e3e874..00000000000 --- a/packages/auth/src/recaptchaverifier/grecaptchamock.js +++ /dev/null @@ -1,286 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Defines the mock grecaptcha utilities used for development - * testing. - */ -goog.provide('fireauth.GRecaptchaMockFactory'); -goog.provide('fireauth.RecaptchaMock'); - -goog.require('fireauth.grecaptcha'); -goog.require('fireauth.util'); -goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); - - -/** - * The mock grecaptcha factory. - * @constructor - * @implements {fireauth.grecaptcha} - */ -fireauth.GRecaptchaMockFactory = function() { - /** - * @const @private {!Object} The hash map - * that stores the widget ID to mock reCAPTCHA instances. - */ - this.map_ = {}; - /** - * @private {number} The current widget ID counter, incremented each time - * a new mock reCAPTCHA is created. - */ - this.counter_ = fireauth.GRecaptchaMockFactory.START_INSTANCE_ID; -}; - - -/** - * @const {number} The start ID of the first created mock reCAPTCHA. - */ -fireauth.GRecaptchaMockFactory.START_INSTANCE_ID = 1000000000000; - - -/** @const {number} The reCAPTCHA expiration time in milliseconds. */ -fireauth.GRecaptchaMockFactory.EXPIRATION_TIME_MS = 60000; - - -/** @const {number} The reCAPTCHA auto solving time in milliseconds. */ -fireauth.GRecaptchaMockFactory.SOLVE_TIME_MS = 500; - - -/** - * @private {?fireauth.GRecaptchaMockFactory} The singleton instance - * for grecaptcha mock object. - */ -fireauth.GRecaptchaMockFactory.instance_ = null; - - -/** - * @return {!fireauth.GRecaptchaMockFactory} The singleton grecaptcha mock - * instance. - */ -fireauth.GRecaptchaMockFactory.getInstance = function() { - // Check if there is an existing instance. Otherwise, create one and cache it. - if (!fireauth.GRecaptchaMockFactory.instance_) { - fireauth.GRecaptchaMockFactory.instance_ = - new fireauth.GRecaptchaMockFactory(); - } - return fireauth.GRecaptchaMockFactory.instance_; -}; - - -/** - * Creates a new instance of the mock reCAPTCHA widget. - * - * @param {(!Element|string)} elementOrId Element or element ID for the - * placeholder to render the reCAPTCHA client. - * @param {!Object} params Parameters for the reCAPTCHA client. - * @return {number} The client ID. - * @override - */ -fireauth.GRecaptchaMockFactory.prototype.render = - function(elementOrId, params) { - this.map_[this.counter_.toString()] = - new fireauth.RecaptchaMock(elementOrId, params); - return this.counter_++; -}; - - -/** - * Resets a reCAPTCHA with the given ID. If an ID is not provided, resets the - * first instance. - * - * @param {number=} opt_id The id of the reCAPTCHA client. Defaults to the first - * widget created if unspecified. - * @override - */ -fireauth.GRecaptchaMockFactory.prototype.reset = function(opt_id) { - var mock = this.getMock_(opt_id); - var id = this.getId_(opt_id); - if (mock && id) { - mock.delete(); - delete this.map_[/** @type {string} */ (id)]; - } -}; - - -/** - * Gets the response for the client with the given ID. If an ID is not - * provided, gets the response for the default client. - * - * @param {number=} opt_id The ID of the reCAPTCHA widget. Defaults to the first - * widget created if unspecified. - * @return {?string} - * @override - */ -fireauth.GRecaptchaMockFactory.prototype.getResponse = function(opt_id) { - var mock = this.getMock_(opt_id); - return mock ? mock.getResponse() : null; -}; - - -/** - * Programmatically triggers the invisible reCAPTCHA. - * - * @param {number=} opt_id The ID of the recaptcha client. Defaults to the first - * widget created if unspecified. - * @override - */ -fireauth.GRecaptchaMockFactory.prototype.execute = function(opt_id) { - var mock = this.getMock_(opt_id); - if (mock) { - mock.execute(); - } -}; - - -/** - * @param {number=} opt_id The optional ID to lookup. - * @return {?fireauth.RecaptchaMock} The corresponding reCAPTCHA mock if found. - * @private - */ -fireauth.GRecaptchaMockFactory.prototype.getMock_ = function(opt_id) { - var id = this.getId_(opt_id); - return id ? this.map_[id] || null : null; -}; - - -/** - * @param {number=} opt_id The optional ID to lookup. - * @return {?string} The corresponding reCAPTCHA mock ID if found. - * @private - */ -fireauth.GRecaptchaMockFactory.prototype.getId_ = function(opt_id) { - var id = typeof opt_id === 'undefined' ? - fireauth.GRecaptchaMockFactory.START_INSTANCE_ID : opt_id; - return id ? id.toString() : null; -}; - - -/** - * Mock single reCAPTCHA instance. - * @param {(!Element|string)} elementOrId Element or element ID for the - * placeholder to render the reCAPTCHA client. - * @param {!Object} params Parameters for the reCAPTCHA client. - * @constructor - */ -fireauth.RecaptchaMock = function(elementOrId, params) { - /** @private {boolean} Whether the instance was deleted. */ - this.deleted_ = false; - /** @const @private {!Object} The reCAPTCHA parameters. */ - this.params_ = params; - /** @private {?string} The simulated response token if available. */ - this.responseToken_ = null; - /** - * @private {?number} The timer ID for response callback/expiration callback - * to trigger. - */ - this.timerId_ = null; - /** @const @private {boolean} Whether the reCAPTCHA is visible or not. */ - this.isVisible_ = this.params_['size'] !== 'invisible'; - /** - * @const @private {?Element} The container or button trigger of the - * reCAPTCHA. - */ - this.element_ = goog.dom.getElement(elementOrId); - var self = this; - /** @private {function(?)} The on click handler for invisible reCAPTCHAs. */ - this.onClickHandler_ = function(event) { - self.execute(); - }; - if (this.isVisible_) { - // For a visible reCAPTCHA, simulate reCAPTCHA continuously solved - // and then expired. - this.execute(); - } else { - // Trigger on button click on when execute is directly called. - goog.events.listen( - this.element_, - goog.events.EventType.CLICK, - this.onClickHandler_); - } -}; - - -/** @return {?string} The current reCAPTCHA response. */ -fireauth.RecaptchaMock.prototype.getResponse = function() { - this.checkIfDeleted_(); - return this.responseToken_; -}; - - -/** Starts the reCAPTCHA mock solving/expiration cycle. */ -fireauth.RecaptchaMock.prototype.execute = function() { - this.checkIfDeleted_(); - var self = this; - if (this.timerId_) { - return; - } - // Wait for expected delay before auto-solving. - this.timerId_ = setTimeout(function() { - // Generate random string as reCAPTCHA response token. - self.responseToken_ = fireauth.util.generateRandomAlphaNumericString(50); - // Trigger developer's callbacks. - var callback = self.params_['callback']; - var expirationCallback = self.params_['expired-callback']; - if (callback) { - try { - callback(self.responseToken_); - } catch (e) {} - } - // Wait for token to expire before triggering expiration callback and - // resetting token response. - self.timerId_ = setTimeout(function() { - self.timerId_ = null; - self.responseToken_ = null; - if (expirationCallback) { - try { - expirationCallback(); - } catch (e) {} - } - if (self.isVisible_) { - self.execute(); - } - }, fireauth.GRecaptchaMockFactory.EXPIRATION_TIME_MS); - }, fireauth.GRecaptchaMockFactory.SOLVE_TIME_MS); -}; - - -/** Deletes the current mock instance. */ -fireauth.RecaptchaMock.prototype.delete = function() { - this.checkIfDeleted_(); - this.deleted_ = true; - clearTimeout(this.timerId_); - this.timerId_ = null; - goog.events.unlisten( - this.element_, - goog.events.EventType.CLICK, - this.onClickHandler_); -}; - - -/** - * Checks whether the instance was deleted. - * @private - */ -fireauth.RecaptchaMock.prototype.checkIfDeleted_ = function() { - // This error should never be thrown externally. - // GRecaptchaMockFactory will ensure that a deleted instance is removed. - if (this.deleted_) { - throw new Error('reCAPTCHA mock was already deleted!'); - } -}; diff --git a/packages/auth/src/recaptchaverifier/loader.js b/packages/auth/src/recaptchaverifier/loader.js deleted file mode 100644 index 71fa8dc457a..00000000000 --- a/packages/auth/src/recaptchaverifier/loader.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Defines the RecaptchaLoader interface used to load grecaptcha - * dependencies. - */ - -goog.provide('fireauth.RecaptchaLoader'); - - -/** - * Defines a generic interface to load grecaptcha dependencies. - * @interface - */ -fireauth.RecaptchaLoader = function() {}; - - -/** - * Loads the grecaptcha client library if it is not loaded and returns a promise - * that resolves on success. If the right conditions are available, will reload - * the dependencies for a specified language code. - * @param {?string} hl The reCAPTCHA language code. - * @return {!goog.Promise} A promise that resolves when - * grecaptcha is loaded. - */ -fireauth.RecaptchaLoader.prototype.loadRecaptchaDeps = - function(hl) {}; - - -/** Decrements the reCAPTCHA instance counter. */ -fireauth.RecaptchaLoader.prototype.clearSingleRecaptcha = - function() {}; diff --git a/packages/auth/src/recaptchaverifier/mockloader.js b/packages/auth/src/recaptchaverifier/mockloader.js deleted file mode 100644 index 46e86e0b9b2..00000000000 --- a/packages/auth/src/recaptchaverifier/mockloader.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Defines the RecaptchaLoader implementation used to mock loading - * of grecaptcha dependencies. - */ - -goog.provide('fireauth.RecaptchaMockLoader'); - -goog.require('fireauth.GRecaptchaMockFactory'); -goog.require('fireauth.RecaptchaLoader'); -goog.require('goog.Promise'); - - -/** - * Defines a mock reCAPTCHA loader by implementing the fireauth.RecaptchaLoader - * interface. - * @constructor - * @implements {fireauth.RecaptchaLoader} - */ -fireauth.RecaptchaMockLoader = function() {}; - - -/** - * Loads the grecaptcha mock library if it is not loaded and returns a promise - * that resolves on success. If the right conditions are available, will reload - * the dependencies for a specified language code. - * @param {?string} hl The reCAPTCHA language code. - * @return {!goog.Promise} A promise that resolves when - * grecaptcha is loaded. - * @override - */ -fireauth.RecaptchaMockLoader.prototype.loadRecaptchaDeps = - function(hl) { - return goog.Promise.resolve( - /** @type {!fireauth.grecaptcha} */ (fireauth.GRecaptchaMockFactory.getInstance())); -}; - - -/** - * Decrements the reCAPTCHA instance counter. - * @override - */ -fireauth.RecaptchaMockLoader.prototype.clearSingleRecaptcha = - function() {}; - - -/** - * @private {?fireauth.RecaptchaMockLoader} The singleton instance - * for the mock reCAPTCHA dependency loader. - */ -fireauth.RecaptchaMockLoader.instance_ = null; - - -/** - * @return {!fireauth.RecaptchaMockLoader} The singleton mock reCAPTCHA - * dependency loader instance. - */ -fireauth.RecaptchaMockLoader.getInstance = function() { - // Check if there is an existing instance. Otherwise create one and cache it. - if (!fireauth.RecaptchaMockLoader.instance_) { - fireauth.RecaptchaMockLoader.instance_ = - new fireauth.RecaptchaMockLoader(); - } - return fireauth.RecaptchaMockLoader.instance_; -}; diff --git a/packages/auth/src/recaptchaverifier/realloader.js b/packages/auth/src/recaptchaverifier/realloader.js deleted file mode 100644 index c37210575ec..00000000000 --- a/packages/auth/src/recaptchaverifier/realloader.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Defines the RecaptchaLoader implementation used to load all - * the grecaptcha dependencies. - */ - -goog.provide('fireauth.RecaptchaRealLoader'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.RecaptchaLoader'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.net.jsloader'); -goog.require('goog.string.Const'); - - -/** - * Utility to help load reCAPTCHA dependencies for specified languages. - * @constructor - * @implements {fireauth.RecaptchaLoader} - */ -fireauth.RecaptchaRealLoader = function() { - /** - * @private {number} The reCAPTCHA instance counter. This is used to track the - * number of reCAPTCHAs rendered on the page. This is needed to allow - * localization of the reCAPTCHA. Localization is applied by loading the - * grecaptcha SDK with the hl field provided. However, this will break - * existing reCAPTCHAs. So we should only support i18n when there are no - * other widgets rendered on this screen. If the developer is already - * using reCAPTCHA in another context, we will disable localization so we - * don't accidentally break existing reCAPTCHA widgets. - */ - this.counter_ = goog.global['grecaptcha'] ? Infinity : 0; - /** @private {?string} The current reCAPTCHA language code. */ - this.hl_ = null; - /** @const @private {string} The reCAPTCHA callback name. */ - this.cbName_ = '__rcb' + Math.floor(Math.random() * 1000000).toString(); -}; - - -/** @private @const {!goog.string.Const} The reCAPTCHA javascript source URL. */ -fireauth.RecaptchaRealLoader.RECAPTCHA_SRC_ = goog.string.Const.from( - 'https://www.google.com/recaptcha/api.js?onload=%{onload}&render=explicit' + - '&hl=%{hl}'); - - -/** - * The default timeout delay (units in milliseconds) for requests loading - * the external reCAPTCHA dependencies. - * @const {!fireauth.util.Delay} - * @private - */ -fireauth.RecaptchaRealLoader.DEFAULT_DEPENDENCY_TIMEOUT_ = - new fireauth.util.Delay(30000, 60000); - - -/** - * Loads the grecaptcha client library if it is not loaded and returns a promise - * that resolves on success. If the right conditions are available, will reload - * the dependencies for a specified language code. - * @param {?string} hl The reCAPTCHA language code. - * @return {!goog.Promise} A promise that resolves when - * grecaptcha is loaded. - * @override - */ -fireauth.RecaptchaRealLoader.prototype.loadRecaptchaDeps = - function(hl) { - var self = this; - return new goog.Promise(function(resolve, reject) { - var timer = setTimeout( - function() { - reject(new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED)); - }, - fireauth.RecaptchaRealLoader.DEFAULT_DEPENDENCY_TIMEOUT_ - .get() - ); - // Load grecaptcha SDK if not already loaded or language changed since last - // load and no other rendered reCAPTCHA is visible, - if (!goog.global['grecaptcha'] || (hl !== self.hl_ && !self.counter_)) { - // reCAPTCHA saves the onload function and applies it on subsequent - // reloads. This means that the callback name has to remain the same. - goog.global[self.cbName_] = function() { - if (!goog.global['grecaptcha']) { - clearTimeout(timer); - // This should not happen. - reject(new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR)); - } else { - // Update the current language code. - self.hl_ = hl; - var render = goog.global['grecaptcha']['render']; - // Wrap grecaptcha.render to keep track of rendered grecaptcha. This - // helps detect if the developer rendered a non - // firebase.auth.RecaptchaVerifier reCAPTCHA. - goog.global['grecaptcha']['render'] = - function(container, parameters) { - var widgetId = render(container, parameters); - // Increment only after render succeeds, in case an error is thrown - // during rendering. - self.counter_++; - return widgetId; - }; - clearTimeout(timer); - resolve(goog.global['grecaptcha']); - } - delete goog.global[self.cbName_]; - }; - // Construct reCAPTCHA URL and on load, run the temporary function. - var url = goog.html.TrustedResourceUrl.format( - fireauth.RecaptchaRealLoader.RECAPTCHA_SRC_, - {'onload': self.cbName_, 'hl': hl || ''}); - // TODO: eventually, replace all dependencies on goog.net.jsloader. - goog.Promise.resolve(goog.net.jsloader.safeLoad(url)) - .thenCatch(function(error) { - clearTimeout(timer); - // In case library fails to load, typically due to a network error, - // reset cached loader to null to force a refresh on a retrial. - reject(new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Unable to load external reCAPTCHA dependencies!')); - }); - } else { - clearTimeout(timer); - resolve(goog.global['grecaptcha']); - } - }); -}; - - -/** - * Decrements the reCAPTCHA instance counter. - * @override - */ -fireauth.RecaptchaRealLoader.prototype.clearSingleRecaptcha = - function() { - this.counter_--; -}; - - -/** - * @private {?fireauth.RecaptchaRealLoader} The singleton instance - * for reCAPTCHA dependency loader. - */ -fireauth.RecaptchaRealLoader.instance_ = null; - - -/** - * @return {!fireauth.RecaptchaRealLoader} The singleton reCAPTCHA - * dependency loader instance. - */ -fireauth.RecaptchaRealLoader.getInstance = function() { - // Check if there is an existing instance. Otherwise create one and cache it. - if (!fireauth.RecaptchaRealLoader.instance_) { - fireauth.RecaptchaRealLoader.instance_ = - new fireauth.RecaptchaRealLoader(); - } - return fireauth.RecaptchaRealLoader.instance_; -}; diff --git a/packages/auth/src/recaptchaverifier/recaptchaverifier.js b/packages/auth/src/recaptchaverifier/recaptchaverifier.js deleted file mode 100644 index 1c2c43b651a..00000000000 --- a/packages/auth/src/recaptchaverifier/recaptchaverifier.js +++ /dev/null @@ -1,509 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the reCAPTCHA app verifier and its base class. The - * former is currently used for web phone authentication whereas the latter is - * used for the mobile app verification web fallback. - */ -goog.provide('fireauth.BaseRecaptchaVerifier'); -goog.provide('fireauth.RecaptchaVerifier'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.RecaptchaMockLoader'); -goog.require('fireauth.RecaptchaRealLoader'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.object'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.array'); -goog.require('goog.dom'); - - -/** - * Creates the firebase base reCAPTCHA app verifier independent of Firebase - * App or Auth instances. - * - * @param {string} apiKey The API key used to initialize the RPC handler for - * querying the Auth backend. - * @param {!Element|string} container The reCAPTCHA container parameter. This - * has different meaning depending on whether the reCAPTCHA is hidden or - * visible. - * @param {?Object=} opt_parameters The optional reCAPTCHA parameters. - * @param {?(function():?string)=} opt_getLanguageCode The language code getter - * function. - * @param {?string=} opt_clientVersion The optional client version to append to - * RPC header. - * @param {?Object=} opt_rpcHandlerConfig The optional RPC handler - * configuration, typically passed when different Auth endpoints are to be - * used. - * @param {boolean=} opt_isTestingMode Whether the reCAPTCHA is to be rendered - * in testing mode. - * @constructor - */ -fireauth.BaseRecaptchaVerifier = function(apiKey, container, opt_parameters, - opt_getLanguageCode, opt_clientVersion, opt_rpcHandlerConfig, - opt_isTestingMode) { - // Set the type readonly property needed for full implementation of the - // firebase.auth.ApplicationVerifier interface. - fireauth.object.setReadonlyProperty(this, 'type', 'recaptcha'); - /** - * @private {?goog.Promise} The cached reCAPTCHA ready response. This is - * null until the first time it is triggered or when an error occurs in - * getting ready. - */ - this.cachedReadyPromise_ = null; - /** @private {?number} The reCAPTCHA widget ID. Null when not rendered. */ - this.widgetId_ = null; - /** @private {boolean} Whether the instance is already destroyed. */ - this.destroyed_ = false; - /** @private {!Element|string} The reCAPTCHA container. */ - this.container_ = container; - /** - * @private {?fireauth.grecaptcha} The reCAPTCHA client library namespace. - */ - this.grecaptcha_ = null; - /** - * @const @private {!fireauth.RecaptchaLoader} The grecaptcha loader. - */ - this.recaptchaLoader_ = !!opt_isTestingMode ? - fireauth.RecaptchaMockLoader.getInstance() : - fireauth.RecaptchaRealLoader.getInstance(); - // If no parameters passed, use default settings. - // Currently, visible recaptcha is the default setting as invisible reCAPTCHA - // is not yet supported by the backend. - /** @private {!Object} The reCAPTCHA parameters. */ - this.parameters_ = opt_parameters || { - 'theme': 'light', - 'type': 'image' - }; - /** @private {!Array|!goog.Promise>} List of - * pending promises. */ - this.pendingPromises_ = []; - if (this.parameters_[fireauth.BaseRecaptchaVerifier.ParamName.SITEKEY]) { - // sitekey should not be provided. - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'sitekey should not be provided for reCAPTCHA as one is ' + - 'automatically provisioned for the current project.'); - } - /** @private {boolean} Whether the reCAPTCHA is invisible or not. */ - this.isInvisible_ = - this.parameters_[fireauth.BaseRecaptchaVerifier.ParamName.SIZE] === - 'invisible'; - // Check if DOM is supported. - if (!fireauth.util.isDOMSupported()) { - throw new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED, - 'RecaptchaVerifier is only supported in a browser HTTP/HTTPS ' + - 'environment with DOM support.'); - } - // reCAPTCHA container must be valid and if visible, not empty. - // An invisible reCAPTCHA will not render in its container. That container - // will execute the reCAPTCHA when it is clicked. - if (!goog.dom.getElement(container) || - (!this.isInvisible_ && goog.dom.getElement(container).hasChildNodes())) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'reCAPTCHA container is either not found or already contains inner ' + - 'elements!'); - } - /** - * @private {!fireauth.RpcHandler} The RPC handler for querying the auth - * backend. - */ - this.rpcHandler_ = new fireauth.RpcHandler( - apiKey, - opt_rpcHandlerConfig || null, - opt_clientVersion || null); - /** - * @private {function():?string} Current language code getter. - */ - this.getLanguageCode_ = opt_getLanguageCode || function() {return null;}; - var self = this; - /** - * @private {!Array} The token change listeners. - */ - this.tokenListeners_ = []; - // Wrap token callback. - var existingCallback = - this.parameters_[fireauth.BaseRecaptchaVerifier.ParamName.CALLBACK]; - this.parameters_[fireauth.BaseRecaptchaVerifier.ParamName.CALLBACK] = - function(response) { - // Dispatch internal event for the token response. - self.dispatchEvent_(response); - if (typeof existingCallback === 'function') { - existingCallback(response); - } else if (typeof existingCallback === 'string') { - // Check if the provided callback is a global function name. - var cb = fireauth.util.getObjectRef(existingCallback, goog.global); - if (typeof cb === 'function') { - // If so, trigger it. - cb(response); - } - } - }; - // Wrap expired token callback. - var existingExpiredCallback = this.parameters_[ - fireauth.BaseRecaptchaVerifier.ParamName.EXPIRED_CALLBACK]; - this.parameters_[fireauth.BaseRecaptchaVerifier.ParamName.EXPIRED_CALLBACK] = - function() { - // Dispatch internal event for the token expiration. - self.dispatchEvent_(null); - if (typeof existingExpiredCallback === 'function') { - existingExpiredCallback(); - } else if (typeof existingExpiredCallback === 'string') { - // Check if the provided expired callback is a global function name. - var cb = fireauth.util.getObjectRef(existingExpiredCallback, goog.global); - if (typeof cb === 'function') { - // If so, trigger it. - cb(); - } - } - }; -}; - - -/** - * grecaptcha parameter names. - * @enum {string} - */ -fireauth.BaseRecaptchaVerifier.ParamName = { - CALLBACK: 'callback', - EXPIRED_CALLBACK: 'expired-callback', - SITEKEY: 'sitekey', - SIZE: 'size' -}; - - -/** - * Dispatches the token change event to all subscribed listeners. - * @param {?string} token The current detected token, null for none. - * @private - */ -fireauth.BaseRecaptchaVerifier.prototype.dispatchEvent_ = function(token) { - for (var i = 0; i < this.tokenListeners_.length; i++) { - try { - this.tokenListeners_[i](token); - } catch (e) { - // If any handler fails, ignore and run next handler. - } - } -}; - - -/** - * Add a reCAPTCHA token change listener. - * @param {function(?string)} listener The token listener to add. - * @private - */ -fireauth.BaseRecaptchaVerifier.prototype.addTokenChangeListener_ = - function(listener) { - this.tokenListeners_.push(listener); -}; - - -/** - * Remove a reCAPTCHA token change listener. - * @param {function(?string)} listener The token listener to remove. - * @private - */ -fireauth.BaseRecaptchaVerifier.prototype.removeTokenChangeListener_ = - function(listener) { - goog.array.removeAllIf(this.tokenListeners_, function(ele) { - return ele == listener; - }); -}; - - -/** - * Takes in a pending promise, saves it and adds a clean up callback which - * forgets the pending promise after it is fulfilled and echoes the promise - * back. - * @param {!goog.Promise<*, *>|!goog.Promise} p The pending promise. - * @return {!goog.Promise<*, *>|!goog.Promise} - * @private - */ -fireauth.BaseRecaptchaVerifier.prototype.registerPendingPromise_ = function(p) { - var self = this; - // Save created promise in pending list. - this.pendingPromises_.push(p); - p.thenAlways(function() { - // When fulfilled, remove from pending list. - goog.array.remove(self.pendingPromises_, p); - }); - // Return the promise. - return p; -}; - - -/** @return {boolean} Whether verifier instance has pending promises. */ -fireauth.BaseRecaptchaVerifier.prototype.hasPendingPromises = function() { - return this.pendingPromises_.length != 0; -}; - - -/** - * Gets the current RecaptchaVerifier in a ready state for rendering by first - * checking that the environment supports a reCAPTCHA, loading reCAPTCHA - * dependencies if not already available and then getting the Firebase project's - * provisioned reCAPTCHA configuration. - * @return {!goog.Promise} The promise that resolves when recaptcha - * is ready for rendering. - * @private - */ -fireauth.BaseRecaptchaVerifier.prototype.isReady_ = function() { - var self = this; - // If previously called, return the cached response. - if (this.cachedReadyPromise_) { - return this.cachedReadyPromise_; - } - this.cachedReadyPromise_ = this.registerPendingPromise_(goog.Promise.resolve() - .then(function() { - // Verify environment first. - // Fail quickly from a worker environment or non-HTTP/HTTPS environment. - if (fireauth.util.isHttpOrHttps() && !fireauth.util.isWorker()) { - // Wait for DOM to be ready as this feature depends on that. - return fireauth.util.onDomReady(); - } else { - throw new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED, - 'RecaptchaVerifier is only supported in a browser HTTP/HTTPS ' + - 'environment.'); - } - }) - .then(function() { - // Load external reCAPTCHA dependencies if not already there, taking - // into account the current language code. - return self.recaptchaLoader_.loadRecaptchaDeps(self.getLanguageCode_()); - }) - .then(function(grecaptcha) { - self.grecaptcha_ = grecaptcha; - // Load Firebase project's reCAPTCHA configuration. - return self.rpcHandler_.getRecaptchaParam(); - }) - .then(function(result) { - // Update the reCAPTCHA parameters. - self.parameters_[fireauth.BaseRecaptchaVerifier.ParamName.SITEKEY] = - result[fireauth.RpcHandler.AuthServerField.RECAPTCHA_SITE_KEY]; - }).thenCatch(function(error) { - // Anytime an error occurs, reset the cached ready promise to rerun on - // retrial. - self.cachedReadyPromise_ = null; - // Rethrow the error. - throw error; - })); - // Return the cached/pending ready promise. - return this.cachedReadyPromise_; -}; - -/** - * Renders the reCAPTCHA and returns the allocated widget ID. - * @return {!goog.Promise} The promise that resolves with the reCAPTCHA - * widget ID when it is rendered. - */ -fireauth.BaseRecaptchaVerifier.prototype.render = function() { - this.checkIfDestroyed_(); - var self = this; - // Get reCAPTCHA ready. - return this.registerPendingPromise_(this.isReady_().then(function() { - if (self.widgetId_ === null) { - // For a visible reCAPTCHA, embed in a wrapper DIV container to allow - // re-rendering in the same developer provided container. - var container = self.container_; - if (!self.isInvisible_) { - // Get outer container (the developer provided container). - var outerContainer = goog.dom.getElement(container); - // Create wrapper temp DIV container. - container = goog.dom.createDom(goog.dom.TagName.DIV); - // Add temp DIV to outer container. - outerContainer.appendChild(container); - } - // If not initialized, initialize reCAPTCHA and return its widget ID. - self.widgetId_ = self.grecaptcha_.render(container, self.parameters_); - } - return self.widgetId_; - })); -}; - - -/** - * Gets the reCAPTCHA ready and waits for the reCAPTCHA token to be available - * before resolving the promise returned. - * @return {!goog.Promise} The promise that resolves with the reCAPTCHA - * token when reCAPTCHA challenge is solved. - */ -fireauth.BaseRecaptchaVerifier.prototype.verify = function() { - // Fail if reCAPTCHA is already destroyed. - this.checkIfDestroyed_(); - var self = this; - // Render reCAPTCHA. - return this.registerPendingPromise_(this.render().then(function(widgetId) { - return new goog.Promise(function(resolve, reject) { - // Get current reCAPTCHA token. - var recaptchaToken = self.grecaptcha_.getResponse(widgetId); - if (recaptchaToken) { - // Unexpired token already available. Resolve pending promise with that - // token. - resolve(recaptchaToken); - } else { - // No token available. Listen to token change. - var cb = function(token) { - if (!token) { - // Ignore token expirations. - return; - } - // Remove temporary token change listener. - self.removeTokenChangeListener_(cb); - // Resolve with new token. - resolve(token); - }; - // Add temporary token change listener. - self.addTokenChangeListener_(cb); - if (self.isInvisible_) { - // Execute invisible reCAPTCHA to force a challenge. - // This should do nothing if already triggered either by developer or - // by a button click. - self.grecaptcha_.execute(/** @type {number} */ (self.widgetId_)); - } - } - }); - })); -}; - - -/** - * Resets the reCAPTCHA widget. - */ -fireauth.BaseRecaptchaVerifier.prototype.reset = function() { - this.checkIfDestroyed_(); - if (this.widgetId_ !== null) { - this.grecaptcha_.reset(this.widgetId_); - } -}; - - -/** - * Throws an error if the reCAPTCHA verifier is already cleared. - * @private - */ -fireauth.BaseRecaptchaVerifier.prototype.checkIfDestroyed_ = function() { - if (this.destroyed_) { - throw new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'RecaptchaVerifier instance has been destroyed.'); - } -}; - - -/** - * Removes the reCAPTCHA from the DOM. - */ -fireauth.BaseRecaptchaVerifier.prototype.clear = function() { - this.checkIfDestroyed_(); - this.destroyed_ = true; - // Decrement reCAPTCHA instance counter. - this.recaptchaLoader_.clearSingleRecaptcha(); - // Cancel all pending promises. - for (var i = 0; i < this.pendingPromises_.length; i++) { - this.pendingPromises_[i].cancel( - 'RecaptchaVerifier instance has been destroyed.'); - } - if (!this.isInvisible_) { - goog.dom.removeChildren(goog.dom.getElement(this.container_)); - } -}; - - -/** - * Creates the Firebase reCAPTCHA app verifier, publicly available, for the - * Firebase app provided, used for web phone authentication. - * This is a subclass of fireauth.BaseRecaptchaVerifier. - * - * @param {!Element|string} container The reCAPTCHA container parameter. This - * has different meaning depending on whether the reCAPTCHA is hidden or - * visible. - * @param {?Object=} opt_parameters The optional reCAPTCHA parameters. - * @param {?firebase.app.App=} opt_app The corresponding Firebase app. - * @constructor - * @extends {fireauth.BaseRecaptchaVerifier} - */ -fireauth.RecaptchaVerifier = function(container, opt_parameters, opt_app) { - var isTestingMode = false; - var apiKey; - try { - /** @private {!firebase.app.App} The corresponding Firebase app instance. */ - this.app_ = opt_app || firebase.app(); - } catch (error) { - throw new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'No firebase.app.App instance is currently initialized.'); - } - // API key is required for web client RPC calls. - if (this.app_.options && this.app_.options['apiKey']) { - apiKey = this.app_.options['apiKey']; - } else { - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_API_KEY); - } - var self = this; - // Construct the language code getter based on the underlying Auth instance. - var getLanguageCode = function() { - var languageCode; - // Get the latest language setting. - // reCAPTCHA does not support updating the language of an already - // rendered reCAPTCHA. Reloading the SDK with the new hl will break - // the existing rendered localized reCAPTCHA. We will need to - // document that a new fireauth.BaseRecaptchaVerifier instance needs - // to be instantiated after the language is updated. Otherwise, the - // old language code will remain active on the existing instance. - try { - languageCode = self.app_['auth']().getLanguageCode(); - } catch (e) { - languageCode = null; - } - return languageCode; - }; - // Get the framework version from Auth instance. - var frameworkVersion = null; - try { - frameworkVersion = this.app_['auth']().getFramework(); - } catch (e) { - // Do nothing. - } - try { - isTestingMode = - this.app_['auth']()['settings']['appVerificationDisabledForTesting']; - } catch (e) { - // Do nothing. - } - // Get the client version based on the Firebase JS version. - var clientFullVersion = firebase.SDK_VERSION ? - fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION, - frameworkVersion) : - null; - // Call the superclass constructor with the computed API key, reCAPTCHA - // container, optional parameters, language code getter, Firebase JS client - // version and the current client configuration endpoints. - fireauth.RecaptchaVerifier.base(this, 'constructor', apiKey, - container, opt_parameters, getLanguageCode, clientFullVersion, - fireauth.constants.getEndpointConfig(fireauth.constants.clientEndpoint), - isTestingMode); -}; -goog.inherits(fireauth.RecaptchaVerifier, fireauth.BaseRecaptchaVerifier); diff --git a/packages/auth/src/rpchandler.js b/packages/auth/src/rpchandler.js deleted file mode 100644 index 8853486c35b..00000000000 --- a/packages/auth/src/rpchandler.js +++ /dev/null @@ -1,3025 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Utility for handling RPC requests to server. - */ -goog.provide('fireauth.RpcHandler'); -goog.provide('fireauth.RpcHandler.ApiMethodHandler'); -goog.provide('fireauth.RpcHandler.VerifyAssertionData'); -goog.provide('fireauth.XmlHttpFactory'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthErrorWithCredential'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.idp'); -goog.require('fireauth.idp.ProviderId'); -goog.require('fireauth.object'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Uri'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.json'); -goog.require('goog.net.CorsXmlHttpFactory'); -goog.require('goog.net.EventType'); -goog.require('goog.net.FetchXmlHttpFactory'); -goog.require('goog.net.XhrIo'); -goog.require('goog.net.XmlHttpFactory'); -goog.require('goog.net.jsloader'); -goog.require('goog.object'); -goog.require('goog.string.Const'); - - - -/** - * Firebase Auth XmlHttpRequest factory. This is useful for environments like - * Node.js where XMLHttpRequest does not exist. XmlHttpFactory would be - * initialized using the polyfill XMLHttpRequest module. - * @param {function(new:XMLHttpRequest)} xmlHttpRequest The xmlHttpRequest - * constructor. - * @constructor - * @extends {goog.net.XmlHttpFactory} - * @final - */ -fireauth.XmlHttpFactory = function(xmlHttpRequest) { - /** - * @private {function(new:XMLHttpRequest)} The underlying XHR reference. - */ - this.xmlHttpRequest_ = xmlHttpRequest; - fireauth.XmlHttpFactory.base(this, 'constructor'); -}; -goog.inherits(fireauth.XmlHttpFactory, goog.net.XmlHttpFactory); - - -/** - * @return {!goog.net.XhrLike|!XMLHttpRequest} A new XhrLike instance. - * @override - */ -fireauth.XmlHttpFactory.prototype.createInstance = function() { - return new this.xmlHttpRequest_(); -}; - - -/** - * @return {!Object} Options describing how XHR objects obtained from this - * factory should be used. - * @override - */ -fireauth.XmlHttpFactory.prototype.internalGetOptions = function() { - return {}; -}; - - - -/** - * Creates an RPC request handler for the project specified by the API key. - * - * @param {string} apiKey The API key. - * @param {?Object=} opt_config The RPC request processor configuration. - * @param {?string=} opt_firebaseClientVersion The optional Firebase client - * version to log with requests to Firebase Auth server. - * @constructor - */ -fireauth.RpcHandler = function(apiKey, opt_config, opt_firebaseClientVersion) { - /** @private {string} The project API key. */ - this.apiKey_ = apiKey; - var config = opt_config || {}; - this.secureTokenEndpoint_ = config['secureTokenEndpoint'] || - fireauth.RpcHandler.SECURE_TOKEN_ENDPOINT_; - /** - * @private @const {!fireauth.util.Delay} The delay for secure token endpoint - * network timeout. - */ - this.secureTokenTimeout_ = config['secureTokenTimeout'] || - fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_TIMEOUT_; - /** @private @const {!Object} The secure token server headers. */ - this.secureTokenHeaders_ = goog.object.clone( - config['secureTokenHeaders'] || - fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_); - /** @private {string} The Firebase Auth endpoint. */ - this.firebaseEndpoint_ = config['firebaseEndpoint'] || - fireauth.RpcHandler.FIREBASE_ENDPOINT_; - /** @private {string} The identity platform endpoint. */ - this.identityPlatformEndpoint_ = config['identityPlatformEndpoint'] || - fireauth.RpcHandler.IDENTITY_PLATFORM_ENDPOINT_; - /** - * @private @const {!fireauth.util.Delay} The delay for Firebase Auth endpoint - * network timeout. - */ - this.firebaseTimeout_ = config['firebaseTimeout'] || - fireauth.RpcHandler.DEFAULT_FIREBASE_TIMEOUT_; - this.firebaseHeaders_ = goog.object.clone( - config['firebaseHeaders'] || - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_); - // If Firebase client version needs to be logged too. - if (opt_firebaseClientVersion) { - // Log client version for Firebase Auth server. - this.firebaseHeaders_['X-Client-Version'] = opt_firebaseClientVersion; - // Log client version for securetoken server. - this.secureTokenHeaders_['X-Client-Version'] = opt_firebaseClientVersion; - } - - // Get XMLHttpRequest reference. - var XMLHttpRequest = fireauth.RpcHandler.getXMLHttpRequest(); - if (!XMLHttpRequest && !fireauth.util.isWorker()) { - // In a Node.js environment, xmlhttprequest module needs to be required. - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'The XMLHttpRequest compatibility library was not found.'); - } - /** @private {!goog.net.XmlHttpFactory|undefined} The XHR factory. */ - this.rpcHandlerXhrFactory_ = undefined; - // Initialize XHR factory. CORS does not apply in native environments or - // workers so don't use CorsXmlHttpFactory in those cases. - if (fireauth.util.isWorker()) { - // For worker environment use FetchXmlHttpFactory. - this.rpcHandlerXhrFactory_ = new goog.net.FetchXmlHttpFactory( - /** @type {!WorkerGlobalScope} */ (self)); - } else if (fireauth.util.isNativeEnvironment()) { - // For Node.js, this is the polyfill library. For other environments, - // this is the native global XMLHttpRequest. - this.rpcHandlerXhrFactory_ = new fireauth.XmlHttpFactory( - /** @type {function(new:XMLHttpRequest)} */ (XMLHttpRequest)); - } else { - // CORS Browser environment. - this.rpcHandlerXhrFactory_ = new goog.net.CorsXmlHttpFactory(); - } - /** @private {?string} The tenant ID. */ - this.tenantId_ = null; -}; - - -/** - * @return {?function(new:XMLHttpRequest)|undefined} The current environment - * XMLHttpRequest. This is undefined for worker environment. - */ -fireauth.RpcHandler.getXMLHttpRequest = function() { - // In Node.js XMLHttpRequest is polyfilled. - var isNode = fireauth.util.getEnvironment() == fireauth.util.Env.NODE; - var XMLHttpRequest = goog.global['XMLHttpRequest'] || - (isNode && - firebase.INTERNAL['node'] && - firebase.INTERNAL['node']['XMLHttpRequest']); - return XMLHttpRequest; -}; - - -/** - * Enums for HTTP request methods. - * @enum {string} - */ -fireauth.RpcHandler.HttpMethod = { - POST: 'POST', - GET: 'GET' -}; - - -/** - * Firebase Auth server error codes. - * @enum {string} - */ -fireauth.RpcHandler.ServerError = { - ADMIN_ONLY_OPERATION: 'ADMIN_ONLY_OPERATION', - CAPTCHA_CHECK_FAILED: 'CAPTCHA_CHECK_FAILED', - CORS_UNSUPPORTED: 'CORS_UNSUPPORTED', - CREDENTIAL_MISMATCH: 'CREDENTIAL_MISMATCH', - CREDENTIAL_TOO_OLD_LOGIN_AGAIN: 'CREDENTIAL_TOO_OLD_LOGIN_AGAIN', - DYNAMIC_LINK_NOT_ACTIVATED: 'DYNAMIC_LINK_NOT_ACTIVATED', - EMAIL_CHANGE_NEEDS_VERIFICATION: 'EMAIL_CHANGE_NEEDS_VERIFICATION', - EMAIL_EXISTS: 'EMAIL_EXISTS', - EMAIL_NOT_FOUND: 'EMAIL_NOT_FOUND', - EXPIRED_OOB_CODE: 'EXPIRED_OOB_CODE', - FEDERATED_USER_ID_ALREADY_LINKED: 'FEDERATED_USER_ID_ALREADY_LINKED', - INVALID_APP_CREDENTIAL: 'INVALID_APP_CREDENTIAL', - INVALID_APP_ID: 'INVALID_APP_ID', - INVALID_CERT_HASH: 'INVALID_CERT_HASH', - INVALID_CODE: 'INVALID_CODE', - INVALID_CONTINUE_URI: 'INVALID_CONTINUE_URI', - INVALID_CUSTOM_TOKEN: 'INVALID_CUSTOM_TOKEN', - INVALID_DYNAMIC_LINK_DOMAIN: 'INVALID_DYNAMIC_LINK_DOMAIN', - INVALID_EMAIL: 'INVALID_EMAIL', - INVALID_ID_TOKEN: 'INVALID_ID_TOKEN', - INVALID_IDP_RESPONSE: 'INVALID_IDP_RESPONSE', - INVALID_IDENTIFIER: 'INVALID_IDENTIFIER', - INVALID_MESSAGE_PAYLOAD: 'INVALID_MESSAGE_PAYLOAD', - INVALID_MFA_PENDING_CREDENTIAL: 'INVALID_MFA_PENDING_CREDENTIAL', - INVALID_OAUTH_CLIENT_ID: 'INVALID_OAUTH_CLIENT_ID', - INVALID_OOB_CODE: 'INVALID_OOB_CODE', - INVALID_PASSWORD: 'INVALID_PASSWORD', - INVALID_PENDING_TOKEN: 'INVALID_PENDING_TOKEN', - INVALID_PHONE_NUMBER: 'INVALID_PHONE_NUMBER', - INVALID_PROVIDER_ID: 'INVALID_PROVIDER_ID', - INVALID_RECIPIENT_EMAIL: 'INVALID_RECIPIENT_EMAIL', - INVALID_SENDER: 'INVALID_SENDER', - INVALID_SESSION_INFO: 'INVALID_SESSION_INFO', - INVALID_TEMPORARY_PROOF: 'INVALID_TEMPORARY_PROOF', - INVALID_TENANT_ID: 'INVALID_TENANT_ID', - MFA_ENROLLMENT_NOT_FOUND: 'MFA_ENROLLMENT_NOT_FOUND', - MISSING_ANDROID_PACKAGE_NAME: 'MISSING_ANDROID_PACKAGE_NAME', - MISSING_APP_CREDENTIAL: 'MISSING_APP_CREDENTIAL', - MISSING_CODE: 'MISSING_CODE', - MISSING_CONTINUE_URI: 'MISSING_CONTINUE_URI', - MISSING_CUSTOM_TOKEN: 'MISSING_CUSTOM_TOKEN', - MISSING_IOS_BUNDLE_ID: 'MISSING_IOS_BUNDLE_ID', - MISSING_MFA_ENROLLMENT_ID: 'MISSING_MFA_ENROLLMENT_ID', - MISSING_MFA_PENDING_CREDENTIAL: 'MISSING_MFA_PENDING_CREDENTIAL', - MISSING_OOB_CODE: 'MISSING_OOB_CODE', - MISSING_OR_INVALID_NONCE: 'MISSING_OR_INVALID_NONCE', - MISSING_PASSWORD: 'MISSING_PASSWORD', - MISSING_PHONE_NUMBER: 'MISSING_PHONE_NUMBER', - MISSING_SESSION_INFO: 'MISSING_SESSION_INFO', - OPERATION_NOT_ALLOWED: 'OPERATION_NOT_ALLOWED', - PASSWORD_LOGIN_DISABLED: 'PASSWORD_LOGIN_DISABLED', - QUOTA_EXCEEDED: 'QUOTA_EXCEEDED', - RESET_PASSWORD_EXCEED_LIMIT: 'RESET_PASSWORD_EXCEED_LIMIT', - REJECTED_CREDENTIAL: 'REJECTED_CREDENTIAL', - SECOND_FACTOR_EXISTS: 'SECOND_FACTOR_EXISTS', - SECOND_FACTOR_LIMIT_EXCEEDED: 'SECOND_FACTOR_LIMIT_EXCEEDED', - SESSION_EXPIRED: 'SESSION_EXPIRED', - TENANT_ID_MISMATCH: 'TENANT_ID_MISMATCH', - TOKEN_EXPIRED: 'TOKEN_EXPIRED', - TOO_MANY_ATTEMPTS_TRY_LATER: 'TOO_MANY_ATTEMPTS_TRY_LATER', - UNSUPPORTED_FIRST_FACTOR: 'UNSUPPORTED_FIRST_FACTOR', - UNSUPPORTED_TENANT_OPERATION: 'UNSUPPORTED_TENANT_OPERATION', - UNVERIFIED_EMAIL: 'UNVERIFIED_EMAIL', - UNAUTHORIZED_DOMAIN: 'UNAUTHORIZED_DOMAIN', - USER_CANCELLED: 'USER_CANCELLED', - USER_DISABLED: 'USER_DISABLED', - USER_NOT_FOUND: 'USER_NOT_FOUND', - WEAK_PASSWORD: 'WEAK_PASSWORD' -}; - - -/** - * A map of server error codes to client errors. - * @typedef {!Object< - * !fireauth.RpcHandler.ServerError, !fireauth.authenum.Error>} - */ -fireauth.RpcHandler.ServerErrorMap; - - -/** - * Firebase Auth response field names. - * @enum {string} - */ -fireauth.RpcHandler.AuthServerField = { - ALL_PROVIDERS: 'allProviders', - AUTH_URI: 'authUri', - AUTHORIZED_DOMAINS: 'authorizedDomains', - DYNAMIC_LINKS_DOMAIN: 'dynamicLinksDomain', - EMAIL: 'email', - ERROR_MESSAGE: 'errorMessage', - EXPIRES_IN: 'expiresIn', - ID_TOKEN: 'idToken', - MFA_PENDING_CREDENTIAL: 'mfaPendingCredential', - NEED_CONFIRMATION: 'needConfirmation', - OAUTH_ID_TOKEN: 'oauthIdToken', - PENDING_TOKEN: 'pendingToken', - PHONE_RESPONSE_INFO: 'phoneResponseInfo', - PHONE_SESSION_INFO: 'phoneSessionInfo', - POST_BODY: 'postBody', - PROVIDER_ID: 'providerId', - RECAPTCHA_SITE_KEY: 'recaptchaSiteKey', - REQUEST_URI: 'requestUri', - REFRESH_TOKEN: 'refreshToken', - SESSION_ID: 'sessionId', - SESSION_INFO: 'sessionInfo', - SIGNIN_METHODS: 'signinMethods', - TEMPORARY_PROOF: 'temporaryProof' -}; - - -/** - * Firebase Auth response injected fields. - * @enum {string} - */ -fireauth.RpcHandler.InjectedResponseField = { - NONCE: 'nonce' -}; - - -/** - * Firebase Auth getOobConfirmationCode requestType possible values. - * @enum {string} - */ -fireauth.RpcHandler.GetOobCodeRequestType = { - EMAIL_SIGNIN: 'EMAIL_SIGNIN', - NEW_EMAIL_ACCEPT: 'NEW_EMAIL_ACCEPT', - PASSWORD_RESET: 'PASSWORD_RESET', - VERIFY_AND_CHANGE_EMAIL: 'VERIFY_AND_CHANGE_EMAIL', - VERIFY_EMAIL: 'VERIFY_EMAIL' -}; - - -/** - * Firebase Auth response field names. - * @enum {string} - */ -fireauth.RpcHandler.StsServerField = { - ACCESS_TOKEN: 'access_token', - EXPIRES_IN: 'expires_in', - REFRESH_TOKEN: 'refresh_token' -}; - - -/** - * @return {string} The API key. - */ -fireauth.RpcHandler.prototype.getApiKey = function() { - return this.apiKey_; -}; - - -/** - * The Firebase custom locale header. - * @const {string} - * @private - */ -fireauth.RpcHandler.FIREBASE_LOCALE_KEY_ = 'X-Firebase-Locale'; - - -/** - * The secure token endpoint. - * @const {string} - * @private - */ -fireauth.RpcHandler.SECURE_TOKEN_ENDPOINT_ = - 'https://securetoken.googleapis.com/v1/token'; - - -/** - * The default timeout delay (units in milliseconds) for requests sending to - * STS token endpoint. - * @const {!fireauth.util.Delay} - * @private - */ -fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_TIMEOUT_ = - new fireauth.util.Delay(30000, 60000); - - -/** - * The STS token RPC content headers. - * @const {!Object} - * @private - */ -fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_ = { - 'Content-Type': 'application/x-www-form-urlencoded' -}; - - -/** - * The Firebase endpoint. - * @const {string} - * @private - */ -fireauth.RpcHandler.FIREBASE_ENDPOINT_ = - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/'; - - -/** - * The Identity Platform endpoint. - * @const {string} - * @private - */ -fireauth.RpcHandler.IDENTITY_PLATFORM_ENDPOINT_ = - 'https://identitytoolkit.googleapis.com/v2/'; - - -/** - * The default timeout delay (units in milliseconds) for requests sending to - * Firebase endpoint. - * @const {!fireauth.util.Delay} - * @private - */ -fireauth.RpcHandler.DEFAULT_FIREBASE_TIMEOUT_ = - new fireauth.util.Delay(30000, 60000); - - -/** - * The Firebase RPC content headers. - * @const {!Object} - * @private - */ -fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_ = { - 'Content-Type': 'application/json' -}; - - -/** - * Updates the custom locale header. - * @param {?string} languageCode The new languageCode. - */ -fireauth.RpcHandler.prototype.updateCustomLocaleHeader = - function(languageCode) { - if (languageCode) { - // If a language code is provided, add it to the header. - this.firebaseHeaders_[fireauth.RpcHandler.FIREBASE_LOCALE_KEY_] = - languageCode; - } else { - // Otherwise remove the custom locale header. - delete this.firebaseHeaders_[fireauth.RpcHandler.FIREBASE_LOCALE_KEY_]; - } -}; - - -/** - * Updates the emulator configuration. - * @param {?fireauth.constants.EmulatorSettings} emulatorConfig The new - * emulator config. - */ -fireauth.RpcHandler.prototype.updateEmulatorConfig = function(emulatorConfig) { - if (!emulatorConfig) { - return; - } - // If an emulator config is provided, update the endpoints. - this.secureTokenEndpoint_ = - fireauth.RpcHandler.generateEmululatorEndpointUrl_( - fireauth.RpcHandler.SECURE_TOKEN_ENDPOINT_, emulatorConfig); - this.firebaseEndpoint_ = - fireauth.RpcHandler.generateEmululatorEndpointUrl_( - fireauth.RpcHandler.FIREBASE_ENDPOINT_, emulatorConfig); - this.identityPlatformEndpoint_ = - fireauth.RpcHandler.generateEmululatorEndpointUrl_( - fireauth.RpcHandler.IDENTITY_PLATFORM_ENDPOINT_, emulatorConfig); -} - - /** - * Creates an endpoint URL intended for use by the emulator. - * @param {string} endpoint the production endpoint URL. - * @param {?fireauth.constants.EmulatorSettings} emulatorConfig The emulator - * config. - * @return {string} The emulator endpoint URL. - * @private - */ -fireauth.RpcHandler.generateEmululatorEndpointUrl_ = function(endpoint, emulatorConfig) { - const uri = goog.Uri.parse(endpoint); - const emulatorUri = goog.Uri.parse(emulatorConfig.url); - uri.setPath(uri.getDomain() + uri.getPath()); - uri.setScheme(emulatorUri.getScheme()); - uri.setDomain(emulatorUri.getDomain()); - uri.setPort(emulatorUri.getPort()); - return uri.toString(); -} - - -/** - * Updates the X-Client-Version in the header. - * @param {?string} clientVersion The new client version. - */ -fireauth.RpcHandler.prototype.updateClientVersion = function(clientVersion) { - if (clientVersion) { - // Update client version for Firebase Auth server. - this.firebaseHeaders_['X-Client-Version'] = clientVersion; - // Update client version for securetoken server. - this.secureTokenHeaders_['X-Client-Version'] = clientVersion; - } else { - // Remove client version from header. - delete this.firebaseHeaders_['X-Client-Version']; - delete this.secureTokenHeaders_['X-Client-Version']; - } -}; - - -/** - * Updates the tenant ID in the request. - * @param {?string} tenantId The new tenant ID. - */ -fireauth.RpcHandler.prototype.updateTenantId = function(tenantId) { - this.tenantId_ = tenantId; -}; - - -/** - * Returns the tenant ID. - * @return {?string} The tenant ID. - */ -fireauth.RpcHandler.prototype.getTenantId = function() { - return this.tenantId_; -}; - - -/** - * Sends XhrIo request using goog.net.XhrIo. - * @param {string} url The URL to make a request to. - * @param {function(?Object)=} opt_callback The callback to run on completion. - * @param {fireauth.RpcHandler.HttpMethod=} opt_httpMethod The HTTP send method. - * @param {?ArrayBuffer|?ArrayBufferView|?Blob|?Document|?FormData|string=} - * opt_data The request content. - * @param {?Object=} opt_headers The request content headers. - * @param {number=} opt_timeout The request timeout. - * @private - */ -fireauth.RpcHandler.prototype.sendXhr_ = function( - url, - opt_callback, - opt_httpMethod, - opt_data, - opt_headers, - opt_timeout) { - var sendXhr; - if (fireauth.util.supportsCors() || fireauth.util.isWorker()) { - // If supports CORS use goog.net.XhrIo. - sendXhr = goog.bind(this.sendXhrUsingXhrIo_, this); - } else { - // Load gapi.client.request and gapi.auth dependency dynamically. - if (!fireauth.RpcHandler.loadGApi_) { - fireauth.RpcHandler.loadGApi_ = - new goog.Promise(function(resolve, reject) { - // On load, resolve. - fireauth.RpcHandler.loadGApiJs_(resolve, reject); - }); - } - // If does not support CORS, use gapi.client.request. - sendXhr = goog.bind(this.sendXhrUsingGApiClient_, this); - } - sendXhr( - url, opt_callback, opt_httpMethod, opt_data, opt_headers, opt_timeout); -}; - - -/** - * Sends XhrIo request using goog.net.XhrIo. - * @param {string} url The URL to make a request to. - * @param {function(?Object)=} opt_callback The callback to run on completion. - * @param {fireauth.RpcHandler.HttpMethod=} opt_httpMethod The HTTP send method. - * @param {?ArrayBuffer|?ArrayBufferView|?Blob|?Document|?FormData|string=} - * opt_data The request content. - * @param {?Object=} opt_headers The request content headers. - * @param {number=} opt_timeout The request timeout. - * @private - */ -fireauth.RpcHandler.prototype.sendXhrUsingXhrIo_ = function( - url, - opt_callback, - opt_httpMethod, - opt_data, - opt_headers, - opt_timeout) { - if (fireauth.util.isWorker() && !fireauth.util.isFetchSupported()) { - throw new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED, - 'fetch, Headers and Request native APIs or equivalent Polyfills ' + - 'must be available to support HTTP requests from a Worker ' + - 'environment.'); - } - var xhrIo = new goog.net.XhrIo(this.rpcHandlerXhrFactory_); - - // xhrIo.setTimeoutInterval not working in IE10 and IE11, handle manually. - var requestTimeout; - if (opt_timeout) { - xhrIo.setTimeoutInterval(opt_timeout); - requestTimeout = setTimeout(function() { - xhrIo.dispatchEvent(goog.net.EventType.TIMEOUT); - }, opt_timeout); - } - // Run callback function on completion. - xhrIo.listen( - goog.net.EventType.COMPLETE, - /** @this {goog.net.XhrIo} */ - function() { - // Clear timeout timer. - if (requestTimeout) { - clearTimeout(requestTimeout); - } - // Response assumed to be in json format. If not, catch, log error and - // pass null to callback. - var response = null; - try { - // Do not use this.responseJson() as it uses goog.json.parse - // underneath. Internal goog.json.parse parsing uses eval and since - // recommended Content Security Policy does not allow unsafe-eval, - // this is failing and throwing an error in chrome extensions and - // warnings else where. Use native parsing instead via JSON.parse. - response = JSON.parse(this.getResponseText()) || null; - } catch (e) { - response = null; - } - if (opt_callback) { - opt_callback(/** @type {?Object} */ (response)); - } - }); - // Dispose xhrIo on ready. - xhrIo.listenOnce( - goog.net.EventType.READY, - /** @this {goog.net.XhrIo} */ - function() { - // Clear timeout timer. - if (requestTimeout) { - clearTimeout(requestTimeout); - } - // Dispose xhrIo. - this.dispose(); - }); - // Listen to timeout error. - // This should work when request is aborted too. - xhrIo.listenOnce( - goog.net.EventType.TIMEOUT, - /** @this {goog.net.XhrIo} */ - function() { - // Clear timeout timer. - if (requestTimeout) { - clearTimeout(requestTimeout); - } - // Dispose xhrIo. - this.dispose(); - // The request timed out. - if (opt_callback) { - opt_callback(null); - } - }); - xhrIo.send(url, opt_httpMethod, opt_data, opt_headers); -}; - - -/** - * @const {!goog.string.Const} The GApi client library URL. - * @private - */ -fireauth.RpcHandler.GAPI_SRC_ = goog.string.Const.from( - 'https://apis.google.com/js/client.js?onload=%{onload}'); - - -/** - * @const {string} - * @private - */ -fireauth.RpcHandler.GAPI_CALLBACK_NAME_ = - '__fcb' + Math.floor(Math.random() * 1000000).toString(); - - -/** - * Loads the GApi client library if it is not loaded. - * @param {function()} callback The callback to invoke once it's loaded. - * @param {function(?Object)} errback The error callback. - * @private - */ -fireauth.RpcHandler.loadGApiJs_ = function(callback, errback) { - // If gapi.client.request not available, load it dynamically. - if (!((window['gapi'] || {})['client'] || {})['request']) { - goog.global[fireauth.RpcHandler.GAPI_CALLBACK_NAME_] = function() { - // Callback will be called by GApi, test properly loaded here instead of - // after jsloader resolves. - if (!((window['gapi'] || {})['client'] || {})['request']) { - errback(new Error(fireauth.RpcHandler.ServerError.CORS_UNSUPPORTED)); - } else { - callback(); - } - }; - var url = goog.html.TrustedResourceUrl.format( - fireauth.RpcHandler.GAPI_SRC_, - {'onload': fireauth.RpcHandler.GAPI_CALLBACK_NAME_}); - // TODO: replace goog.net.jsloader with our own script includer. - var result = goog.net.jsloader.safeLoad(url); - result.addErrback(function() { - // In case file fails to load. - errback(new Error(fireauth.RpcHandler.ServerError.CORS_UNSUPPORTED)); - }); - } else { - callback(); - } -}; - - -/** - * Sends XhrIo request using gapi.client. - * @param {string} url The URL to make a request to. - * @param {function(?Object)=} opt_callback The callback to run on completion. - * @param {fireauth.RpcHandler.HttpMethod=} opt_httpMethod The HTTP send method. - * @param {?ArrayBuffer|?ArrayBufferView|?Blob|?Document|?FormData|string=} - * opt_data The request content. - * @param {?Object=} opt_headers The request content headers. - * @param {number=} opt_timeout The request timeout. - * @private - */ -fireauth.RpcHandler.prototype.sendXhrUsingGApiClient_ = function( - url, - opt_callback, - opt_httpMethod, - opt_data, - opt_headers, - opt_timeout) { - var self = this; - // Wait for GApi dependency to load. - fireauth.RpcHandler.loadGApi_.then(function() { - window['gapi']['client']['setApiKey'](self.getApiKey()); - // GApi maintains the Auth result and automatically append the Auth token to - // all outgoing requests. Firebase Auth requests will be rejected if there - // are others scopes (e.g. google plus) for the Auth token. Need to empty - // the token before call gitkit api. Restored in callback. - var oauth2Token = window['gapi']['auth']['getToken'](); - window['gapi']['auth']['setToken'](null); - window['gapi']['client']['request']({ - 'path': url, - 'method': opt_httpMethod, - 'body': opt_data, - 'headers': opt_headers, - // This needs to be set to none, otherwise the access token will be passed - // in the header field causing apiary to complain. - 'authType': 'none', - 'callback': function(response) { - window['gapi']['auth']['setToken'](oauth2Token); - if (opt_callback) { - opt_callback(response); - } - } - }); - }).thenCatch(function(error) { - // Catches failure to support CORS and propagates it. - if (opt_callback) { - // Simulate backend server error to be caught by upper layer. - opt_callback({ - 'error': { - 'message': (error && error['message']) || - fireauth.RpcHandler.ServerError.CORS_UNSUPPORTED - } - }); - } - }); -}; - - -/** - * Validates the request for the STS access token. - * - * @param {?Object} data The STS token request body. - * @return {boolean} Whether the request is valid. - * @private - */ -fireauth.RpcHandler.prototype.validateStsTokenRequest_ = function(data) { - if (data['grant_type'] == 'refresh_token' && data['refresh_token']) { - // Exchange refresh token. - return true; - } else if (data['grant_type'] == 'authorization_code' && data['code']) { - // Exchange ID token. - return true; - } else { - // Invalid. - return false; - } -}; - - -/** - * Handles the request for the STS access token. - * - * @param {!Object} data The STS token request body. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.requestStsToken = function(data) { - var self = this; - return new goog.Promise(function(resolve, reject) { - if (self.validateStsTokenRequest_(data)) { - self.sendXhr_( - self.secureTokenEndpoint_ + '?key=' + - encodeURIComponent(self.getApiKey()), - function(response) { - if (!response) { - // An unparseable response from the XHR most likely indicates some - // problem with the network. - reject(new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED)); - } else if (fireauth.RpcHandler.hasError_(response)) { - reject(fireauth.RpcHandler.getDeveloperError_(response)); - } else if ( - !response[fireauth.RpcHandler.StsServerField.ACCESS_TOKEN] || - !response[fireauth.RpcHandler.StsServerField.REFRESH_TOKEN]) { - reject(new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR)); - } else { - resolve(response); - } - }, - fireauth.RpcHandler.HttpMethod.POST, - goog.Uri.QueryData.createFromMap(data).toString(), - self.secureTokenHeaders_, - self.secureTokenTimeout_.get()); - } else { - reject(new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)); - } - }); -}; - - -/** - * @param {!Object} data The object to serialize. - * @return {string} The serialized object with null, undefined and empty string - * values removed. - * @private - */ -fireauth.RpcHandler.serialize_ = function(data) { - // goog.json.serialize converts undefined values to null. - // This helper removes all empty strings, nulls and undefined from serialized - // object. - // Serialize trimmed data. - return goog.json.serialize(fireauth.util.copyWithoutNullsOrUndefined(data)); -}; - - -/** - * Creates and executes a request for the given API method using the legacy - * Firebase Auth endpoint. - * @param {string} method The API method. - * @param {!fireauth.RpcHandler.HttpMethod} httpMethod The http request method. - * @param {!Object} data The data for the API request. In the case of a GET - * request, the contents of this object will be form encoded and appended - * to the query string of the URL. No post body is sent in that case. If an - * object value is specified, it will be converted to a string: - * encodeURIComponent(String(value)). - * @param {?fireauth.RpcHandler.ServerErrorMap=} opt_customErrorMap A map - * of server error codes to client errors to override default error - * handling. - * @param {boolean=} opt_cachebuster Whether to append a unique string to - * request to force backend to return an uncached response to request. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.requestFirebaseEndpoint = function( - method, httpMethod, data, opt_customErrorMap, opt_cachebuster) { - return this.requestAuthEndpoint_( - this.firebaseEndpoint_, method, httpMethod, data, opt_customErrorMap, - opt_cachebuster); -}; - - -/** - * Creates and executes a request for the given API method using the identity - * platform endpoint. - * @param {string} method The API method. - * @param {!fireauth.RpcHandler.HttpMethod} httpMethod The http request method. - * @param {!Object} data The data for the API request. In the case of a GET - * request, the contents of this object will be form encoded and appended - * to the query string of the URL. No post body is sent in that case. If an - * object value is specified, it will be converted to a string: - * encodeURIComponent(String(value)). - * @param {?fireauth.RpcHandler.ServerErrorMap=} opt_customErrorMap A map - * of server error codes to client errors to override default error - * handling. - * @param {boolean=} opt_cachebuster Whether to append a unique string to - * request to force backend to return an uncached response to request. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.requestIdentityPlatformEndpoint = function( - method, httpMethod, data, opt_customErrorMap, opt_cachebuster) { - return this.requestAuthEndpoint_( - this.identityPlatformEndpoint_, method, httpMethod, data, - opt_customErrorMap, opt_cachebuster); -}; - - -/** - * Creates and executes a request for the given API method and Auth endpoint. - * @param {string} endpoint The Auth endpoint to use. - * @param {string} method The API method. - * @param {!fireauth.RpcHandler.HttpMethod} httpMethod The http request method. - * @param {!Object} data The data for the API request. In the case of a GET - * request, the contents of this object will be form encoded and appended - * to the query string of the URL. No post body is sent in that case. If an - * object value is specified, it will be converted to a string: - * encodeURIComponent(String(value)). - * @param {?fireauth.RpcHandler.ServerErrorMap=} opt_customErrorMap A map - * of server error codes to client errors to override default error - * handling. - * @param {boolean=} opt_cachebuster Whether to append a unique string to - * request to force backend to return an uncached response to request. - * @return {!goog.Promise} - * @private - */ -fireauth.RpcHandler.prototype.requestAuthEndpoint_ = function( - endpoint, method, httpMethod, data, opt_customErrorMap, opt_cachebuster) { - var self = this; - // Construct endpoint URL. - var uri = goog.Uri.parse(endpoint + method); - uri.setParameterValue('key', this.getApiKey()); - // Check whether to append cachebuster to request. - if (opt_cachebuster) { - uri.setParameterValue('cb', Date.now().toString()); - } - // Firebase allows GET endpoints. - var isGet = httpMethod == fireauth.RpcHandler.HttpMethod.GET; - if (isGet) { - // For GET HTTP method, append data to query string. - for (var key in data) { - if (data.hasOwnProperty(key)) { - uri.setParameterValue(key, data[key]); - } - } - } - return new goog.Promise(function(resolve, reject) { - self.sendXhr_( - uri.toString(), - function(response) { - if (!response) { - // An unparseable response from the XHR most likely indicates some - // problem with the network. - reject(new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED)); - } else if (fireauth.RpcHandler.hasError_(response)) { - reject(fireauth.RpcHandler.getDeveloperError_(response, - opt_customErrorMap || {})); - } else { - resolve(response); - } - }, - httpMethod, - // No post body data in GET requests. - isGet ? undefined : fireauth.RpcHandler.serialize_(data), - self.firebaseHeaders_, - self.firebaseTimeout_.get()); - }); -}; - - -/** - * Verifies that the request has a valid email set. - * @param {!Object} request - * @private - */ -fireauth.RpcHandler.validateRequestHasEmail_ = function(request) { - if (!fireauth.util.isValidEmailAddress(request['email'])) { - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_EMAIL); - } -}; - - -/** - * Verifies that the response has a valid email set. - * @param {!Object} response - * @private - */ -fireauth.RpcHandler.validateResponseHasEmail_ = function(response) { - if (!fireauth.util.isValidEmailAddress(response['email'])) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Verifies that the an email is valid, if it is there. - * @param {!Object} request - * @private - */ -fireauth.RpcHandler.validateEmailIfPresent_ = function(request) { - if ('email' in request) { - fireauth.RpcHandler.validateRequestHasEmail_(request); - } -}; - - -/** - * @param {string} providerId The provider ID. - * @param {?Array=} opt_additionalScopes The list of scope strings. - * @return {?string} The IDP and its comma separated scope strings serialized. - * @private - */ -fireauth.RpcHandler.getAdditionalScopes_ = - function(providerId, opt_additionalScopes) { - var scopes = {}; - if (opt_additionalScopes && opt_additionalScopes.length) { - scopes[providerId] = opt_additionalScopes.join(','); - // Return stringified scopes. - return goog.json.serialize(scopes); - } - return null; -}; - - -/** - * Validates a response from getAuthUri. - * @param {?Object} response The getAuthUri response data. - * @private - */ -fireauth.RpcHandler.validateGetAuthResponse_ = function(response) { - if (!response[fireauth.RpcHandler.AuthServerField.AUTH_URI]) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'Unable to determine the authorization endpoint for the specified '+ - 'provider. This may be an issue in the provider configuration.'); - } else if ( !response[fireauth.RpcHandler.AuthServerField.SESSION_ID]) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Requests createAuthUri endpoint to retrieve the authUri and session ID for - * the start of an OAuth handshake. - * @param {string} providerId The provider ID. - * @param {string} continueUri The IdP callback URL. - * @param {?Object=} opt_customParameters The optional OAuth custom parameters - * plain object. - * @param {?Array=} opt_additionalScopes The list of scope strings. - * @param {?string=} opt_email The optional email. - * @param {?string=} opt_sessionId The optional session ID. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.getAuthUri = function( - providerId, - continueUri, - opt_customParameters, - opt_additionalScopes, - opt_email, - opt_sessionId) { - // SAML provider request is constructed differently than OAuth requests. - var isSaml = fireauth.idp.isSaml(providerId); - var request = { - 'identifier': opt_email, - 'providerId': providerId, - 'continueUri': continueUri, - 'customParameter': opt_customParameters || {}, - 'oauthScope': fireauth.RpcHandler.getAdditionalScopes_( - providerId, opt_additionalScopes), - 'sessionId': opt_sessionId - }; - // Custom parameters and OAuth scopes should be ignored. - if (isSaml) { - delete request['customParameter']; - delete request['oauthScope']; - } - // When sessionId is provided, mobile flow (Cordova) is being used, force - // code flow and not implicit flow. All other providers use code flow by - // default. - if (opt_sessionId && providerId == fireauth.idp.ProviderId.GOOGLE) { - request['authFlowType'] = 'CODE_FLOW'; - } - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.GET_AUTH_URI, - request); -}; - - -/** - * Gets the list of IDPs that can be used to log in for the given identifier. - * @param {string} identifier The identifier, such as an email address. - * @return {!goog.Promise>} - */ -fireauth.RpcHandler.prototype.fetchProvidersForIdentifier = - function(identifier) { - // createAuthUri returns an error if continue URI is not http or https. - // For environments like Cordova, Chrome extensions, native frameworks, file - // systems, etc, use http://localhost as continue URL. - var continueUri = fireauth.util.isHttpOrHttps() ? - fireauth.util.getCurrentUrl() : 'http://localhost'; - var request = { - 'identifier': identifier, - 'continueUri': continueUri - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.CREATE_AUTH_URI, request) - .then(function(response) { - return response[fireauth.RpcHandler.AuthServerField.ALL_PROVIDERS] || - []; - }); -}; - - -/** - * Returns the list of sign in methods for the given identifier. - * @param {string} identifier The identifier, such as an email address. - * @return {!goog.Promise>} - */ -fireauth.RpcHandler.prototype.fetchSignInMethodsForIdentifier = function( - identifier) { - // createAuthUri returns an error if continue URI is not http or https. - // For environments like Cordova, Chrome extensions, native frameworks, file - // systems, etc, use http://localhost as continue URL. - var continueUri = fireauth.util.isHttpOrHttps() ? - fireauth.util.getCurrentUrl() : - 'http://localhost'; - var request = { - 'identifier': identifier, - 'continueUri': continueUri - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.CREATE_AUTH_URI, request) - .then(function(response) { - return response[fireauth.RpcHandler.AuthServerField.SIGNIN_METHODS] || - []; - }); -}; - - -/** - * Gets the list of authorized domains for the specified project. - * @return {!goog.Promise>} - */ -fireauth.RpcHandler.prototype.getAuthorizedDomains = function() { - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.GET_PROJECT_CONFIG, {}) - .then(function(response) { - return response[ - fireauth.RpcHandler.AuthServerField.AUTHORIZED_DOMAINS] || []; - }); -}; - - -/** - * Gets the reCAPTCHA parameters needed to render the project's provisioned - * reCAPTCHA. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.getRecaptchaParam = function() { - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.GET_RECAPTCHA_PARAM, {}); -}; - - -/** - * Gets the list of authorized domains for the specified project. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.getDynamicLinkDomain = function() { - var request = { - 'returnDynamicLink': true - }; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.RETURN_DYNAMIC_LINK, request); -}; - - -/** - * Checks if the provided iOS bundle ID belongs to the project as specified by - * the API key. - * @param {string} iosBundleId The iOS bundle ID to check. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.isIosBundleIdValid = function(iosBundleId) { - var request = { - 'iosBundleId': iosBundleId - }; - // This will either resolve if the identifier is valid or throw INVALID_APP_ID - // if not. - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.GET_PROJECT_CONFIG, request) - .then(function(result) { - // Do not return anything. - }); -}; - - -/** - * Checks if the provided Android package name belongs to the project as - * specified by the API key. - * @param {string} androidPackageName The iOS bundle ID to check. - * @param {?string=} opt_sha1Cert The optional SHA-1 Android cert to check. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.isAndroidPackageNameValid = - function(androidPackageName, opt_sha1Cert) { - var request = { - 'androidPackageName': androidPackageName - }; - // This is relevant for the native Android SDK flow. - // This will redirect to an FDL domain owned by GMScore instead of - // the developer's FDL domain as is done for Cordova apps. - if (!!opt_sha1Cert) { - request['sha1Cert'] = opt_sha1Cert; - } - // When no sha1Cert is passed, this will either resolve if the identifier is - // valid or throw INVALID_APP_ID if not. - // When sha1Cert is also passed, this will either resolve or fail with an - // INVALID_CERT_HASH error. - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.GET_PROJECT_CONFIG, request) - .then(function(result) { - // Do not return anything. - }); -}; - - -/** - * Checks if the provided OAuth client ID belongs to the project as specified by - * the API key. - * @param {string} clientId The OAuth client ID to check. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.isOAuthClientIdValid = function(clientId) { - var request = { - 'clientId': clientId - }; - // This will either resolve if the client ID is valid or throw - // INVALID_OAUTH_CLIENT_ID if not. - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.GET_PROJECT_CONFIG, request) - .then(function(result) { - // Do not return anything. - }); -}; - - -/** - * Requests getAccountInfo endpoint using an ID token. - * @param {string} idToken The ID token. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.getAccountInfoByIdToken = function(idToken) { - var request = {'idToken': idToken}; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.GET_ACCOUNT_INFO, - request); -}; - - -/** - * Validates a request to sign in with email and password. - * @param {!Object} request - * @private - */ -fireauth.RpcHandler.validateVerifyCustomTokenRequest_ = function(request) { - if (!request['token']) { - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_CUSTOM_TOKEN); - } -}; - - -/** - * Verifies a custom token and returns a Promise that resolves with the ID - * token. - * @param {string} token The custom token. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyCustomToken = function(token) { - var request = {'token': token}; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.VERIFY_CUSTOM_TOKEN, - request); -}; - - -/** - * Validates a request to sign in with email and password. - * @param {!Object} request - * @private - */ -fireauth.RpcHandler.validateVerifyPasswordRequest_ = function(request) { - fireauth.RpcHandler.validateRequestHasEmail_(request); - if (!request['password']) { - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_PASSWORD); - } -}; - - -/** - * Verifies a password and returns a Promise that resolves with the ID - * token. - * @param {string} email The email address. - * @param {string} password The entered password. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyPassword = function(email, password) { - var request = { - 'email': email, - 'password': password - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.VERIFY_PASSWORD, - request); -}; - - -/** - * Verifies an email link OTP for sign-in and returns a Promise that resolves - * with the ID token. - * @param {string} email The email address. - * @param {string} oobCode The email action OTP. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.emailLinkSignIn = function(email, oobCode) { - var request = { - 'email': email, - 'oobCode': oobCode - }; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.EMAIL_LINK_SIGNIN, request); -}; - - -/** - * Verifies an email link OTP for linking and returns a Promise that resolves - * with the ID token. - * @param {string} idToken The ID token. - * @param {string} email The email address. - * @param {string} oobCode The email action OTP. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.emailLinkSignInForLinking = - function(idToken, email, oobCode) { - var request = { - 'idToken': idToken, - 'email': email, - 'oobCode': oobCode - }; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.EMAIL_LINK_SIGNIN_FOR_LINKING, - request); -}; - - -/** - * Validates a response that should contain an ID token. - * If no ID token is available, it checks if a multi-factor pending credential - * is available instead. In that case, it throws the MFA_REQUIRED error code. - * @param {?Object} response The server response data. - * @private - */ -fireauth.RpcHandler.validateIdTokenResponse_ = function(response) { - if (!response[fireauth.RpcHandler.AuthServerField.ID_TOKEN]) { - // User could be a second factor user. - // When second factor is required, a pending credential is returned. - if (response[fireauth.RpcHandler.AuthServerField.MFA_PENDING_CREDENTIAL]) { - throw new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - goog.object.clone(response)); - } - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Validates a getRecaptchaParam response. - * @param {?Object} response The server response data. - * @private - */ -fireauth.RpcHandler.validateGetRecaptchaParamResponse_ = function(response) { - // Both are required. This could change though. - if (!response[fireauth.RpcHandler.AuthServerField.RECAPTCHA_SITE_KEY]) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Validates a request that sends the verification ID and code for a sign in/up - * phone Auth flow. - * @param {!Object} request The server request object. - * @private - */ -fireauth.RpcHandler.validateVerifyPhoneNumberRequest_ = function(request) { - // There are 2 cases here: - // case 1: sessionInfo and code - // case 2: phoneNumber and temporaryProof - if (request['phoneNumber'] || request['temporaryProof']) { - // Case 2. Both phoneNumber and temporaryProof should be set. - if (!request['phoneNumber'] || !request['temporaryProof']) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - } else { - // Otherwise it's case 1, so we expect sessionInfo and code. - if (!request['sessionInfo']) { - throw new fireauth.AuthError( - fireauth.authenum.Error.MISSING_SESSION_INFO); - } - if (!request['code']) { - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE); - } - } -}; - - -/** - * Validates a request that sends the verification ID and code for a link/update - * phone Auth flow. - * @param {!Object} request The server request object. - * @private - */ -fireauth.RpcHandler.validateVerifyPhoneNumberLinkRequest_ = function(request) { - // idToken should be required here. - if (!request['idToken']) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - // The other request parameters match the sign in flow. - fireauth.RpcHandler.validateVerifyPhoneNumberRequest_(request); -}; - - -/** - * Validates a request to create an email and password account. - * @param {!Object} request - * @private - */ -fireauth.RpcHandler.validateCreateAccountRequest_ = function(request) { - fireauth.RpcHandler.validateRequestHasEmail_(request); - if (!request['password']) { - throw new fireauth.AuthError(fireauth.authenum.Error.WEAK_PASSWORD); - } -}; - - -/** - * Validates a request to createAuthUri. - * @param {!Object} request - * @private - */ -fireauth.RpcHandler.validateGetAuthUriRequest_ = function(request) { - if (!request['continueUri']) { - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_CONTINUE_URI); - } - // Either a SAML or non SAML providerId must be provided. - if (!request['providerId']) { - throw new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'A provider ID must be provided in the request.'); - } -}; - - -/** - * Creates an email/password account. Returns a Promise that resolves with the - * ID token. - * @param {string} email The email address of the account. - * @param {string} password The password. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.createAccount = function(email, password) { - var request = { - 'email': email, - 'password': password - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.CREATE_ACCOUNT, - request); -}; - - -/** - * Signs in a user as anonymous. Returns a Promise that resolves with the - * ID token. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.signInAnonymously = function() { - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.SIGN_IN_ANONYMOUSLY, {}); -}; - - -/** - * Deletes the user's account corresponding to the idToken given. - * @param {string} idToken The idToken of the user. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.deleteAccount = function(idToken) { - var request = { - 'idToken': idToken - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.DELETE_ACCOUNT, - request); -}; - - -/** - * Requests setAccountInfo endpoint for updateEmail operation. - * @param {string} idToken The ID token. - * @param {string} newEmail The new email. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.updateEmail = function(idToken, newEmail) { - var request = { - 'idToken': idToken, - 'email': newEmail - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.SET_ACCOUNT_INFO, - request); -}; - - -/** - * Validates a setAccountInfo request that updates the password. - * @param {!Object} request - * @private - */ -fireauth.RpcHandler.validateSetAccountInfoSensitive_ = function(request) { - fireauth.RpcHandler.validateEmailIfPresent_(request); - if (!request['password']) { - throw new fireauth.AuthError(fireauth.authenum.Error.WEAK_PASSWORD); - } -}; - - -/** - * Requests setAccountInfo endpoint for updatePassword operation. - * @param {string} idToken The ID token. - * @param {string} newPassword The new password. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.updatePassword = function(idToken, newPassword) { - var request = { - 'idToken': idToken, - 'password': newPassword - }; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.SET_ACCOUNT_INFO_SENSITIVE, request); -}; - - -/** - * Requests setAccountInfo endpoint to set the email and password. This can be - * used to link an existing account to a new email and password account. - * @param {string} idToken The ID token. - * @param {string} newEmail The new email. - * @param {string} newPassword The new password. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.updateEmailAndPassword = function(idToken, - newEmail, newPassword) { - var request = { - 'idToken': idToken, - 'email': newEmail, - 'password': newPassword - }; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.SET_ACCOUNT_INFO_SENSITIVE, request); -}; - - -/** - * Maps the name of a field in the account info object to the backend enum - * value, for deletion of profile fields. - * @private {!Object} - */ -fireauth.RpcHandler.PROFILE_FIELD_TO_ENUM_NAME_ = { - 'displayName': 'DISPLAY_NAME', - 'photoUrl': 'PHOTO_URL' -}; - - -/** - * Updates the profile of the user. When resolved, promise returns a response - * similar to that of getAccountInfo. - * @param {string} idToken The ID token of the user whose profile is changing. - * @param {!Object} profileData The new profile data. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.updateProfile = function(idToken, profileData) { - var data = { - 'idToken': idToken - }; - var fieldsToDelete = []; - - // Copy over the relevant fields from profileData, or explicitly flag a field - // for deletion if null is passed as the value. Note that this currently only - // checks profileData to the first level. - goog.object.forEach(fireauth.RpcHandler.PROFILE_FIELD_TO_ENUM_NAME_, - function(enumName, fieldName) { - var fieldValue = profileData[fieldName]; - if (fieldValue === null) { - // If null is explicitly provided, delete the field. - fieldsToDelete.push(enumName); - } else if (fieldName in profileData) { - // If the field is explicitly set, send it to the backend. - data[fieldName] = fieldValue; - } - }); - if (fieldsToDelete.length) { - data['deleteAttribute'] = fieldsToDelete; - } - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.SET_ACCOUNT_INFO, data); -}; - - -/** - * Validates a request for an email action code for password reset. - * @param {!Object} request The getOobCode request data for password reset. - * @private - */ -fireauth.RpcHandler.validateOobCodeRequest_ = function(request) { - if (request['requestType'] != - fireauth.RpcHandler.GetOobCodeRequestType.PASSWORD_RESET) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - fireauth.RpcHandler.validateRequestHasEmail_(request); -}; - - -/** - * Validates a request for an email action for passwordless email sign-in. - * @param {!Object} request The getOobCode request data for email sign-in. - * @private - */ -fireauth.RpcHandler.validateEmailSignInCodeRequest_ = function(request) { - if (request['requestType'] != - fireauth.RpcHandler.GetOobCodeRequestType.EMAIL_SIGNIN) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - fireauth.RpcHandler.validateRequestHasEmail_(request); -}; - - -/** - * Validates a request for an email action for email verification. - * @param {!Object} request The getOobCode request data for email verification. - * @private - */ -fireauth.RpcHandler.validateEmailVerificationCodeRequest_ = function(request) { - if (request['requestType'] != - fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_EMAIL) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Validates a request for an email action for email verification before update. - * @param {!Object} request The getOobCode request data for email verification - * before update. - * @private - */ -fireauth.RpcHandler.validateEmailVerificationCodeBeforeUpdateRequest_ = - function(request) { - if (request['requestType'] != - fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_AND_CHANGE_EMAIL) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Requests getOobCode endpoint for password reset, returns promise that - * resolves with user's email. - * @param {string} email The email account with the password to be reset. - * @param {!Object} additionalRequestData Additional data to add to the request. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.sendPasswordResetEmail = - function(email, additionalRequestData) { - var request = { - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.PASSWORD_RESET, - 'email': email - }; - // Extend the original request with the additional data. - goog.object.extend(request, additionalRequestData); - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.GET_OOB_CODE, request); -}; - - -/** - * Requests getOobCode endpoint for passwordless email sign-in, returns promise - * that resolves with user's email. - * @param {string} email The email account to sign in with. - * @param {!Object} additionalRequestData Additional data to add to the request. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.sendSignInLinkToEmail = function( - email, additionalRequestData) { - var request = { - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.EMAIL_SIGNIN, - 'email': email - }; - // Extend the original request with the additional data. - goog.object.extend(request, additionalRequestData); - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.GET_EMAIL_SIGNIN_CODE, request); -}; - - -/** - * Requests getOobCode endpoint for email verification, returns promise that - * resolves with user's email. - * @param {string} idToken The idToken of the user confirming his email. - * @param {!Object} additionalRequestData Additional data to add to the request. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.sendEmailVerification = - function(idToken, additionalRequestData) { - var request = { - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_EMAIL, - 'idToken': idToken - }; - // Extend the original request with the additional data. - goog.object.extend(request, additionalRequestData); - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.GET_EMAIL_VERIFICATION_CODE, request); -}; - - -/** - * Requests getOobCode endpoint for email verification before updating the - * email. - * @param {string} idToken The idToken of the user updating his email. - * @param {string} newEmail The new email. - * @param {!Object} additionalRequestData Additional data to add to the request. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyBeforeUpdateEmail = - function(idToken, newEmail, additionalRequestData) { - var request = { - 'requestType': - fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_AND_CHANGE_EMAIL, - 'idToken': idToken, - 'newEmail': newEmail - }; - // Extend the original request with the additional data. - goog.object.extend(request, additionalRequestData); - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.GET_EMAIL_VERIFICATION_CODE_BEFORE_UPDATE, - request); -}; - - -/** - * Requests sendVerificationCode endpoint for verifying the user's ownership of - * a phone number. It resolves with a sessionInfo (verificationId). - * @param {!Object} request The verification request which contains a phone - * number and an assertion. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.sendVerificationCode = function(request) { - // In the future, we could support other types of assertions so for now, - // we are keeping the request an object. - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.SEND_VERIFICATION_CODE, request); -}; - - -/** - * Requests verifyPhoneNumber endpoint for sign in/sign up phone number - * authentication flow and resolves with the STS token response. - * @param {!Object} request The phone number ID and code to exchange for a - * Firebase Auth session. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyPhoneNumber = function(request) { - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.VERIFY_PHONE_NUMBER, request); -}; - - -/** - * Requests verifyPhoneNumber endpoint for link/update phone number - * authentication flow and resolves with the STS token response. - * @param {!Object} request The phone number ID and code to exchange for a - * Firebase Auth session. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyPhoneNumberForLinking = function(request) { - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.VERIFY_PHONE_NUMBER_FOR_LINKING, request); -}; - - -/** - * Validates a response to a phone number linking request. - * @param {?Object} response The server response data. - * @private - */ -fireauth.RpcHandler.validateVerifyPhoneNumberForLinkingResponse_ = - function(response) { - if (response[fireauth.RpcHandler.AuthServerField.TEMPORARY_PROOF]) { - response['code'] = fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE; - throw fireauth.AuthErrorWithCredential.fromPlainObject(response); - } - - // If there's no temporary proof, then we expect the request to have - // succeeded and returned an ID token. - fireauth.RpcHandler.validateIdTokenResponse_(response); -}; - - -/** - * Requests verifyPhoneNumber endpoint for reauthenticating with a phone number - * and resolves with the STS token response. - * @param {!Object} request The phone number ID, code, and current ID token to - * exchange for a refreshed Firebase Auth session. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyPhoneNumberForExisting = function(request) { - request['operation'] = 'REAUTH'; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.VERIFY_PHONE_NUMBER_FOR_EXISTING, - request); -}; - - -/** - * Validates a request for starting phone MFA enrollment. - * @param {!Object} request The startPhoneMfaEnrollment request data. - * @private - */ -fireauth.RpcHandler.validateStartPhoneMfaEnrollmentRequest_ = - function(request) { - if (!request['phoneEnrollmentInfo']) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - if (!request['phoneEnrollmentInfo']['phoneNumber']) { - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_PHONE_NUMBER); - } - if (!request['phoneEnrollmentInfo']['recaptchaToken']) { - throw new fireauth.AuthError( - fireauth.authenum.Error.MISSING_APP_CREDENTIAL); - } -}; - - -/** - * Validates a response to a start phone MFA enrollment request. - * @param {!Object} response The server response data. - * @private - */ -fireauth.RpcHandler.validateStartPhoneMfaEnrollmentResponse_ = - function(response) { - if (!response[fireauth.RpcHandler.AuthServerField.PHONE_SESSION_INFO] || - !response[fireauth.RpcHandler.AuthServerField.PHONE_SESSION_INFO] - [fireauth.RpcHandler.AuthServerField.SESSION_INFO]) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Requests startMfaEnrollment endpoint for verifying the user's ownership of - * a phone number before enrolling to MFA. It resolves with a sessionInfo - * (verificationId) string. - * @param {!Object} request The enroll MFA request for phone. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.startPhoneMfaEnrollment = function(request) { - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.START_PHONE_MFA_ENROLLMENT, request) - .then(function(response) { - // Extract the sessionInfo(verificationId) from response. - return response[fireauth.RpcHandler.AuthServerField.PHONE_SESSION_INFO] - [fireauth.RpcHandler.AuthServerField.SESSION_INFO]; - }); -}; - - -/** - * Validates a request for finalizing phone MFA enrollment or sign-in. - * @param {!Object} request The finalizePhoneMfaEnrollment or - * finalizePhoneMfaSignIn request data. - * @private - */ -fireauth.RpcHandler.validateFinalizePhoneMfaRequest_ = function(request) { - if (!request['phoneVerificationInfo']) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } - if (!request['phoneVerificationInfo']['sessionInfo']) { - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_SESSION_INFO); - } - if (!request['phoneVerificationInfo']['code']) { - throw new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE); - } -}; - - -/** - * Requests finalizeMfaEnrollment endpoint to finish the enrollment flow for - * phone MFA. It resolves with the MFA token response. - * @param {!Object} request The enroll MFA request for phone. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.finalizePhoneMfaEnrollment = function(request) { - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.FINALIZE_PHONE_MFA_ENROLLMENT, request); -}; - - -/** - * Validates a request for starting phone MFA sign-in. - * @param {!Object} request The startPhoneMfaSignIn request data. - * @private - */ -fireauth.RpcHandler.validateStartPhoneMfaSignInRequest_ = function(request) { - if (!request['phoneSignInInfo'] || - !request['phoneSignInInfo']['recaptchaToken']) { - throw new fireauth.AuthError( - fireauth.authenum.Error.MISSING_APP_CREDENTIAL); - } -}; - - -/** - * Validates a response to a start phone MFA sign-in request. - * @param {!Object} response The server response data. - * @private - */ -fireauth.RpcHandler.validateStartPhoneMfaSignInResponse_ = function(response) { - if (!response[fireauth.RpcHandler.AuthServerField.PHONE_RESPONSE_INFO] || - !response[fireauth.RpcHandler.AuthServerField.PHONE_RESPONSE_INFO] - [fireauth.RpcHandler.AuthServerField.SESSION_INFO]) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Requests startMfaSignIn endpoint for verifying the user's ownership of - * a phone number before signing in to MFA. It resolves with a sessionInfo - * (verificationId) string. - * @param {!Object} request The sign in MFA request for phone. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.startPhoneMfaSignIn = function(request) { - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.START_PHONE_MFA_SIGN_IN, request) - .then(function(response) { - // Extract the sessionInfo(verificationId) from response. - return response[fireauth.RpcHandler.AuthServerField.PHONE_RESPONSE_INFO] - [fireauth.RpcHandler.AuthServerField.SESSION_INFO]; - }); -}; - - -/** - * Requests finalizeMfaSignIn endpoint to finish the sign-in flow for - * phone MFA. It resolves with the MFA token response. - * @param {!Object} request The sign in MFA request for phone. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.finalizePhoneMfaSignIn = function(request) { - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.FINALIZE_PHONE_MFA_SIGN_IN, request); -}; - - -/** - * Validates the response for unenrolling a second factor. If the user is still - * considered signed in, the endpoint returns new tokens. However, if the - * user session is revoked no tokens will be returned. - * @param {!Object} response The server response data. - * @private - */ -fireauth.RpcHandler.validateWithdrawMfaResponse_ = function(response) { - var hasIdToken = !!response[fireauth.RpcHandler.AuthServerField.ID_TOKEN]; - var hasRefreshToken = - !!response[fireauth.RpcHandler.AuthServerField.REFRESH_TOKEN]; - - if (hasIdToken ^ hasRefreshToken) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Requests the withdraw endpoint to unenroll a second factor. Returns new - * tokens if the user is still considered signed in or no tokens if the user - * session is revoked and the user is being signed out. - * @param {string} idToken The ID token. - * @param {string} mfaEnrollmentId The MFA enrollment ID. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.withdrawMfa = function(idToken, mfaEnrollmentId) { - var request = { - 'idToken': idToken, - 'mfaEnrollmentId': mfaEnrollmentId - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.WITHDRAW_MFA, request); -}; - - -/** - * The custom error map for reauth with verifyPhoneNumber. - * @private {!fireauth.RpcHandler.ServerErrorMap} - */ -fireauth.RpcHandler.verifyPhoneNumberForExistingErrorMap_ = {}; - -// For most RPCs, the backend error USER_NOT_FOUND means that the sent STS -// token is invalid. However, for this specific case, USER_NOT_FOUND actually -// means that the sent credential is invalid. -fireauth.RpcHandler.verifyPhoneNumberForExistingErrorMap_[ - fireauth.RpcHandler.ServerError.USER_NOT_FOUND] = - fireauth.authenum.Error.USER_DELETED; - - -/** - * Validates a request to deleteLinkedAccounts. - * @param {?Object} request - * @private - */ -fireauth.RpcHandler.validateDeleteLinkedAccountsRequest_ = function(request) { - if (!Array.isArray(request['deleteProvider'])) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Updates the providers for the account associated with the idToken. - * @param {string} idToken The ID token. - * @param {!Array} providersToDelete The array of providers to delete. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.deleteLinkedAccounts = - function(idToken, providersToDelete) { - var request = { - 'idToken': idToken, - 'deleteProvider': providersToDelete - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.DELETE_LINKED_ACCOUNTS, - request); -}; - - -/** - * Validates a verifyAssertion request. - * @param {?Object} request The verifyAssertion request data. - * @private - */ -fireauth.RpcHandler.validateVerifyAssertionRequest_ = function(request) { - // Either (requestUri and sessionId), (requestUri and postBody) or - // (requestUri and pendingToken) are required. - if (!request[fireauth.RpcHandler.AuthServerField.REQUEST_URI] || - (!request[fireauth.RpcHandler.AuthServerField.SESSION_ID] && - !request[fireauth.RpcHandler.AuthServerField.POST_BODY] && - !request[fireauth.RpcHandler.AuthServerField.PENDING_TOKEN])) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Processes the verifyAssertion response and injects the same raw nonce - * if available in request. - * @param {!Object} request The verifyAssertion request data. - * @param {!Object} response The original verifyAssertion response data. - * @return {!Object} The modified verifyAssertion response. - * @private - */ -fireauth.RpcHandler.processVerifyAssertionResponse_ = - function(request, response) { - // This makes it possible for OIDC providers to: - // 1. Initialize an OIDC Auth credential on successful response. - // 2. Initialize an OIDC Auth credential within the recovery error. - - // When request has sessionId and response has OIDC ID token and no pending - // token, a credential with raw nonce and OIDC ID token needs to be returned. - if (response[fireauth.RpcHandler.AuthServerField.OAUTH_ID_TOKEN] && - response[fireauth.RpcHandler.AuthServerField.PROVIDER_ID] && - response[fireauth.RpcHandler.AuthServerField.PROVIDER_ID] - .indexOf(fireauth.constants.OIDC_PREFIX) == 0 && - // Use pendingToken instead of idToken and rawNonce when available. - !response[fireauth.RpcHandler.AuthServerField.PENDING_TOKEN]) { - if (request[fireauth.RpcHandler.AuthServerField.SESSION_ID]) { - // For full OAuth flow, the nonce is in the session ID. - response[fireauth.RpcHandler.InjectedResponseField.NONCE] = - request[fireauth.RpcHandler.AuthServerField.SESSION_ID]; - } else if (request[fireauth.RpcHandler.AuthServerField.POST_BODY]) { - // For credential flow, the nonce is in the postBody nonce field. - var queryData = new goog.Uri.QueryData( - request[fireauth.RpcHandler.AuthServerField.POST_BODY]); - if (queryData.containsKey( - fireauth.RpcHandler.InjectedResponseField.NONCE)) { - response[fireauth.RpcHandler.InjectedResponseField.NONCE] = - queryData.get(fireauth.RpcHandler.InjectedResponseField.NONCE); - } - } - } - return response; -}; - - -/** - * Validates a response from verifyAssertionForExisting. - * @param {?Object} response The verifyAssertionForExisting response data. - * @private - */ -fireauth.RpcHandler.validateVerifyAssertionForExistingResponse_ = - function(response) { - // When returnIdpCredential is set to true and the account is new, no error - // is thrown but an errorMessage is added to the response. No idToken is - // passed. - if (response[fireauth.RpcHandler.AuthServerField.ERROR_MESSAGE] && - response[fireauth.RpcHandler.AuthServerField.ERROR_MESSAGE] == - fireauth.RpcHandler.ServerError.USER_NOT_FOUND) { - // This corresponds to user-not-found. - throw new fireauth.AuthError(fireauth.authenum.Error.USER_DELETED); - } else if (response[fireauth.RpcHandler.AuthServerField.ERROR_MESSAGE]) { - // Construct developer facing error message from server code in errorMessage - // field. - throw fireauth.RpcHandler.getDeveloperErrorFromCode_( - response[fireauth.RpcHandler.AuthServerField.ERROR_MESSAGE]); - } - // Need confirmation should not be returned when do not create new user flag - // is set. - // Validate if ID token or multi-factor pending credential is available. - fireauth.RpcHandler.validateIdTokenResponse_(response); -}; - - -/** - * Validates a response from verifyAssertion. - * @param {?Object} response The verifyAssertion response data. - * @private - */ -fireauth.RpcHandler.validateVerifyAssertionResponse_ = function(response) { - var error = null; - if (response[fireauth.RpcHandler.AuthServerField.NEED_CONFIRMATION]) { - // Account linking required, previously logged in to another account - // with same email. User must authenticate they are owners of the - // first account. - // If enough info for Auth linking error, throw an instance of Auth linking - // error. This will be used by developer after reauthenticating with email - // provided by error to link using the credentials in Auth linking error. - // If missing information, return regular Auth error. - response['code'] = fireauth.authenum.Error.NEED_CONFIRMATION; - error = fireauth.AuthErrorWithCredential.fromPlainObject(response); - } else if (response[fireauth.RpcHandler.AuthServerField.ERROR_MESSAGE] == - fireauth.RpcHandler.ServerError.FEDERATED_USER_ID_ALREADY_LINKED) { - // When FEDERATED_USER_ID_ALREADY_LINKED returned in error message, auth - // credential and email will also be returned, throw relevant error in that - // case. - // In this case the developer needs to signInWithCredential to the returned - // credentials. - response['code'] = fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE; - error = fireauth.AuthErrorWithCredential.fromPlainObject(response); - } else if (response[fireauth.RpcHandler.AuthServerField.ERROR_MESSAGE] == - fireauth.RpcHandler.ServerError.EMAIL_EXISTS) { - // When EMAIL_EXISTS returned in error message, Auth credential and email - // will also be returned, throw relevant error in that case. - // In this case, the developers needs to sign in the user to the original - // owner of the account and then link to the returned credential here. - response['code'] = fireauth.authenum.Error.EMAIL_EXISTS; - error = fireauth.AuthErrorWithCredential.fromPlainObject(response); - } else if (response[fireauth.RpcHandler.AuthServerField.ERROR_MESSAGE]) { - // Construct developer facing error message from server code in errorMessage - // field. - error = fireauth.RpcHandler.getDeveloperErrorFromCode_( - response[fireauth.RpcHandler.AuthServerField.ERROR_MESSAGE]); - } - // If error found, throw it. - if (error) { - throw error; - } - // Validate if ID token or multi-factor pending credential is available. - fireauth.RpcHandler.validateIdTokenResponse_(response); -}; - - -/** - * Validates a verifyAssertion with linking request. - * @param {?Object} request The verifyAssertion request data. - * @private - */ -fireauth.RpcHandler.validateVerifyAssertionLinkRequest_ = function(request) { - // idToken with either (requestUri and sessionId) or (requestUri and postBody) - // are required. - fireauth.RpcHandler.validateVerifyAssertionRequest_(request); - if (!request['idToken']) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * @typedef {{ - * autoCreate: (boolean|undefined), - * requestUri: string, - * postBody: (?string|undefined), - * pendingIdToken: (?string|undefined), - * sessionId: (?string|undefined), - * idToken: (?string|undefined), - * returnIdpCredential: (boolean|undefined), - * tenantId: (?string|undefined) - * }} - */ -fireauth.RpcHandler.VerifyAssertionData; - - -/** - * Requests verifyAssertion endpoint. When resolved, promise returns the whole - * response. - * @param {!fireauth.RpcHandler.VerifyAssertionData} request - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyAssertion = function(request) { - // Force Auth credential to be returned on the following errors: - // FEDERATED_USER_ID_ALREADY_LINKED - // EMAIL_EXISTS - request['returnIdpCredential'] = true; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.VERIFY_ASSERTION, - request); -}; - - -/** - * Requests verifyAssertion endpoint for federated account linking. When - * resolved, promise returns the whole response. - * @param {!fireauth.RpcHandler.VerifyAssertionData} request - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyAssertionForLinking = function(request) { - // Force Auth credential to be returned on the following errors: - // FEDERATED_USER_ID_ALREADY_LINKED - // EMAIL_EXISTS - request['returnIdpCredential'] = true; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.VERIFY_ASSERTION_FOR_LINKING, - request); -}; - - -/** - * Requests verifyAssertion endpoint for an existing federated account. When - * resolved, promise returns the whole response. If not existing, a - * user-not-found error is thrown. - * @param {!fireauth.RpcHandler.VerifyAssertionData} request - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.verifyAssertionForExisting = function(request) { - // Since we are setting returnIdpCredential to true, a response will be - // returned even though the account doesn't exist but an error message is - // appended with value set to USER_NOT_FOUND. If this flag is not passed, only - // the USER_NOT_FOUND error is thrown without any response. - request['returnIdpCredential'] = true; - // Do not create a new account if the user doesn't exist. - request['autoCreate'] = false; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.VERIFY_ASSERTION_FOR_EXISTING, - request); -}; - - -/** - * Validates a request that should contain an action code. - * @param {!Object} request - * @private - */ -fireauth.RpcHandler.validateApplyActionCodeRequest_ = function(request) { - if (!request['oobCode']) { - throw new fireauth.AuthError(fireauth.authenum.Error.INVALID_OOB_CODE); - } -}; - - -/** - * Validates that a checkActionCode response contains the email and requestType - * fields. - * @param {!Object} response The raw response returned by the server. - * @private - */ -fireauth.RpcHandler.validateCheckActionCodeResponse_ = function(response) { - // If the code is invalid, usually a clear error would be returned. - // In this case, something unexpected happened. - // Email could be empty only if the request type is EMAIL_SIGNIN. - var operation = response['requestType']; - if (!operation || - (!response['email'] && operation != 'EMAIL_SIGNIN' && - operation != 'VERIFY_AND_CHANGE_EMAIL')) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - } -}; - - -/** - * Requests resetPassword endpoint for password reset, returns promise that - * resolves with user's email. - * @param {string} code The email action code to confirm for password reset. - * @param {string} newPassword The new password. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.confirmPasswordReset = - function(code, newPassword) { - var request = { - 'oobCode': code, - 'newPassword': newPassword - }; - return this.invokeRpc(fireauth.RpcHandler.ApiMethod.RESET_PASSWORD, request); -}; - - -/** - * Checks the validity of an email action code and returns the response - * received. - * @param {string} code The email action code to check. - * @return {!goog.Promise} - */ -fireauth.RpcHandler.prototype.checkActionCode = function(code) { - var request = { - 'oobCode': code - }; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.CHECK_ACTION_CODE, request); -}; - - -/** - * Applies an out-of-band email action code, such as an email verification code. - * @param {string} code The email action code. - * @return {!goog.Promise} A promise that resolves with the user's - * email. - */ -fireauth.RpcHandler.prototype.applyActionCode = function(code) { - var request = { - 'oobCode': code - }; - return this.invokeRpc( - fireauth.RpcHandler.ApiMethod.APPLY_OOB_CODE, request); -}; - - -/** - * The specification of an RPC call. The fields are: - *
    - *
  • cachebuster: defines whether to send a unique string with request to - * force the backend to return an uncached response to request. - *
  • customErrorMap: A map of backend error codes to client-side errors. - * Any entries set here override the default handling of the backend error - * code. - *
  • endpoint: defines the backend endpoint to call. - *
  • httpMethod: defines the HTTP method to use, defaulting to POST if not - * specified. - *
  • requestRequiredFields: an array of the fields that are required in the - * request. The RPC call will fail with an INTERNAL_ERROR error if a - * required field is not present or if it is null, undefined, or the empty - * string. - *
  • requestValidator: a function that takes in the request object and throws - * an error if the request is invalid. - *
  • responsePreprocessor: a function to modify the response before running - * validation. The function takes in the request and response object. - *
  • responseValidator: a function that takes in the response object and - * throws an error if the response is invalid. - *
  • responseField: the field of the response object that will be returned - * from the RPC call. If no field is specified, the entire response object - * will be returned. - *
  • returnSecureToken: Set to true to explicitly request STS tokens instead - * of legacy Google Identity Toolkit tokens from the backend. - *
  • requireTenantId: Set to true to send tenant ID to backend in the request. - *
  • useIdentityPlatformEndpoint: Whether to use new identity platform - * endpoints. The default is false. - *
- * @typedef {{ - * cachebuster: (boolean|undefined), - * customErrorMap: (!fireauth.RpcHandler.ServerErrorMap|undefined), - * endpoint: string, - * httpMethod: (!fireauth.RpcHandler.HttpMethod|undefined), - * requestRequiredFields: (!Array|undefined), - * requestValidator: (function(!Object):void|undefined), - * responsePreprocessor: ((function(!Object, !Object):!Object)|undefined), - * responseValidator: (function(!Object):void|undefined), - * responseField: (string|undefined), - * returnSecureToken: (boolean|undefined), - * requireTenantId: (boolean|undefined), - * useIdentityPlatformEndpoint: (boolean|undefined) - * }} - */ -fireauth.RpcHandler.ApiMethodHandler; - - -/** - * The specifications for the backend API methods. - * @enum {!fireauth.RpcHandler.ApiMethodHandler} - */ -fireauth.RpcHandler.ApiMethod = { - APPLY_OOB_CODE: { - endpoint: 'setAccountInfo', - requestValidator: fireauth.RpcHandler.validateApplyActionCodeRequest_, - responseField: fireauth.RpcHandler.AuthServerField.EMAIL, - requireTenantId: true - }, - CHECK_ACTION_CODE: { - endpoint: 'resetPassword', - requestValidator: fireauth.RpcHandler.validateApplyActionCodeRequest_, - responseValidator: fireauth.RpcHandler.validateCheckActionCodeResponse_, - requireTenantId: true - }, - CREATE_ACCOUNT: { - endpoint: 'signupNewUser', - requestValidator: fireauth.RpcHandler.validateCreateAccountRequest_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - returnSecureToken: true, - requireTenantId: true - }, - CREATE_AUTH_URI: { - endpoint: 'createAuthUri', - requireTenantId: true - }, - DELETE_ACCOUNT: { - endpoint: 'deleteAccount', - requestRequiredFields: ['idToken'] - }, - DELETE_LINKED_ACCOUNTS: { - endpoint: 'setAccountInfo', - requestRequiredFields: ['idToken', 'deleteProvider'], - requestValidator: fireauth.RpcHandler.validateDeleteLinkedAccountsRequest_ - }, - EMAIL_LINK_SIGNIN: { - endpoint: 'emailLinkSignin', - requestRequiredFields: ['email', 'oobCode'], - requestValidator: fireauth.RpcHandler.validateRequestHasEmail_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - returnSecureToken: true, - requireTenantId: true - }, - EMAIL_LINK_SIGNIN_FOR_LINKING: { - endpoint: 'emailLinkSignin', - requestRequiredFields: ['idToken', 'email', 'oobCode'], - requestValidator: fireauth.RpcHandler.validateRequestHasEmail_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - returnSecureToken: true - }, - FINALIZE_PHONE_MFA_ENROLLMENT: { - endpoint: 'accounts/mfaEnrollment:finalize', - requestRequiredFields: ['idToken', 'phoneVerificationInfo'], - requestValidator: - fireauth.RpcHandler.validateFinalizePhoneMfaRequest_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - requireTenantId: true, - useIdentityPlatformEndpoint: true - }, - FINALIZE_PHONE_MFA_SIGN_IN: { - endpoint: 'accounts/mfaSignIn:finalize', - requestRequiredFields: ['mfaPendingCredential', 'phoneVerificationInfo'], - requestValidator: - fireauth.RpcHandler.validateFinalizePhoneMfaRequest_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - requireTenantId: true, - useIdentityPlatformEndpoint: true - }, - GET_ACCOUNT_INFO: { - endpoint: 'getAccountInfo' - }, - GET_AUTH_URI: { - endpoint: 'createAuthUri', - requestValidator: fireauth.RpcHandler.validateGetAuthUriRequest_, - responseValidator: fireauth.RpcHandler.validateGetAuthResponse_, - requireTenantId: true - }, - GET_EMAIL_SIGNIN_CODE: { - endpoint: 'getOobConfirmationCode', - requestRequiredFields: ['requestType'], - requestValidator: fireauth.RpcHandler.validateEmailSignInCodeRequest_, - responseField: fireauth.RpcHandler.AuthServerField.EMAIL, - requireTenantId: true - }, - GET_EMAIL_VERIFICATION_CODE: { - endpoint: 'getOobConfirmationCode', - requestRequiredFields: ['idToken', 'requestType'], - requestValidator: fireauth.RpcHandler.validateEmailVerificationCodeRequest_, - responseField: fireauth.RpcHandler.AuthServerField.EMAIL, - requireTenantId: true - }, - GET_EMAIL_VERIFICATION_CODE_BEFORE_UPDATE: { - endpoint: 'getOobConfirmationCode', - requestRequiredFields: ['idToken', 'newEmail', 'requestType'], - requestValidator: - fireauth.RpcHandler.validateEmailVerificationCodeBeforeUpdateRequest_, - responseField: fireauth.RpcHandler.AuthServerField.EMAIL, - requireTenantId: true - }, - GET_OOB_CODE: { - endpoint: 'getOobConfirmationCode', - requestRequiredFields: ['requestType'], - requestValidator: fireauth.RpcHandler.validateOobCodeRequest_, - responseField: fireauth.RpcHandler.AuthServerField.EMAIL, - requireTenantId: true - }, - GET_PROJECT_CONFIG: { - // Microsoft edge caching bug. There are two getProjectConfig API calls, - // first from top level window and then from iframe. The second call has a - // response of 304 which means it's a cached response. We suspect the call - // from iframe is reusing the response from the first call and checks the - // allowed origin in the cached response, which only contains the domain for - // the top level window. - cachebuster: true, - endpoint: 'getProjectConfig', - httpMethod: fireauth.RpcHandler.HttpMethod.GET - }, - GET_RECAPTCHA_PARAM: { - cachebuster: true, - endpoint: 'getRecaptchaParam', - httpMethod: fireauth.RpcHandler.HttpMethod.GET, - responseValidator: fireauth.RpcHandler.validateGetRecaptchaParamResponse_ - }, - RESET_PASSWORD: { - endpoint: 'resetPassword', - requestValidator: fireauth.RpcHandler.validateApplyActionCodeRequest_, - responseField: fireauth.RpcHandler.AuthServerField.EMAIL, - requireTenantId: true - }, - RETURN_DYNAMIC_LINK: { - cachebuster: true, - endpoint: 'getProjectConfig', - httpMethod: fireauth.RpcHandler.HttpMethod.GET, - responseField: fireauth.RpcHandler.AuthServerField.DYNAMIC_LINKS_DOMAIN - }, - SEND_VERIFICATION_CODE: { - endpoint: 'sendVerificationCode', - // Currently only reCAPTCHA tokens supported. - requestRequiredFields: ['phoneNumber', 'recaptchaToken'], - responseField: fireauth.RpcHandler.AuthServerField.SESSION_INFO, - requireTenantId: true - }, - SET_ACCOUNT_INFO: { - endpoint: 'setAccountInfo', - requestRequiredFields: ['idToken'], - requestValidator: fireauth.RpcHandler.validateEmailIfPresent_, - returnSecureToken: true // Maybe updating email will invalidate token in the - // future, this will prevent breaking the client. - }, - SET_ACCOUNT_INFO_SENSITIVE: { - endpoint: 'setAccountInfo', - requestRequiredFields: ['idToken'], - requestValidator: fireauth.RpcHandler.validateSetAccountInfoSensitive_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - returnSecureToken: true // Updating password will send back new sts tokens. - }, - SIGN_IN_ANONYMOUSLY: { - endpoint: 'signupNewUser', - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - returnSecureToken: true, - requireTenantId: true - }, - START_PHONE_MFA_ENROLLMENT: { - endpoint: 'accounts/mfaEnrollment:start', - requestRequiredFields: ['idToken', 'phoneEnrollmentInfo'], - requestValidator: - fireauth.RpcHandler.validateStartPhoneMfaEnrollmentRequest_, - responseValidator: - fireauth.RpcHandler.validateStartPhoneMfaEnrollmentResponse_, - requireTenantId: true, - useIdentityPlatformEndpoint: true - }, - START_PHONE_MFA_SIGN_IN: { - endpoint: 'accounts/mfaSignIn:start', - requestRequiredFields: ['mfaPendingCredential', 'mfaEnrollmentId', - 'phoneSignInInfo'], - requestValidator: - fireauth.RpcHandler.validateStartPhoneMfaSignInRequest_, - responseValidator: - fireauth.RpcHandler.validateStartPhoneMfaSignInResponse_, - requireTenantId: true, - useIdentityPlatformEndpoint: true - }, - VERIFY_ASSERTION: { - endpoint: 'verifyAssertion', - requestValidator: fireauth.RpcHandler.validateVerifyAssertionRequest_, - responsePreprocessor: fireauth.RpcHandler.processVerifyAssertionResponse_, - responseValidator: fireauth.RpcHandler.validateVerifyAssertionResponse_, - returnSecureToken: true, - // Tenant ID is required for this endpoint. But for - // signInWithRedirect/Popup APIs, to make createAuthUri call and - // verifyAssertion call atomic, the tenant ID on RPC handler will be - // overridden by the tenant ID passed directly from the - // request, which is retrieved from Auth event. For signInWithCredential - // API, the tenant ID will still be retrieved from the RPC handler. - requireTenantId: true - }, - VERIFY_ASSERTION_FOR_EXISTING: { - endpoint: 'verifyAssertion', - requestValidator: fireauth.RpcHandler.validateVerifyAssertionRequest_, - responsePreprocessor: fireauth.RpcHandler.processVerifyAssertionResponse_, - responseValidator: - fireauth.RpcHandler.validateVerifyAssertionForExistingResponse_, - returnSecureToken: true, - // Tenant ID is required for this endpoint. But for - // reauthenticateWithRedirect/Popup APIs, to make createAuthUri call and - // verifyAssertion call atomic, the tenant ID on RPC handler will be - // overridden by the tenant ID passed directly from the - // request, which is retrieved from Auth event. For - // reauthenticateWithCredential API, the tenant ID will still be retrieved - // from the RPC handler. - requireTenantId: true - }, - VERIFY_ASSERTION_FOR_LINKING: { - endpoint: 'verifyAssertion', - requestValidator: fireauth.RpcHandler.validateVerifyAssertionLinkRequest_, - responsePreprocessor: fireauth.RpcHandler.processVerifyAssertionResponse_, - responseValidator: fireauth.RpcHandler.validateVerifyAssertionResponse_, - returnSecureToken: true - }, - VERIFY_CUSTOM_TOKEN: { - endpoint: 'verifyCustomToken', - requestValidator: fireauth.RpcHandler.validateVerifyCustomTokenRequest_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - returnSecureToken: true, - requireTenantId: true - }, - VERIFY_PASSWORD: { - endpoint: 'verifyPassword', - requestValidator: fireauth.RpcHandler.validateVerifyPasswordRequest_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - returnSecureToken: true, - requireTenantId: true - }, - VERIFY_PHONE_NUMBER: { - endpoint: 'verifyPhoneNumber', - requestValidator: fireauth.RpcHandler.validateVerifyPhoneNumberRequest_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - requireTenantId: true - }, - VERIFY_PHONE_NUMBER_FOR_LINKING: { - endpoint: 'verifyPhoneNumber', - requestValidator: fireauth.RpcHandler.validateVerifyPhoneNumberLinkRequest_, - responseValidator: - fireauth.RpcHandler.validateVerifyPhoneNumberForLinkingResponse_ - }, - VERIFY_PHONE_NUMBER_FOR_EXISTING: { - customErrorMap: fireauth.RpcHandler.verifyPhoneNumberForExistingErrorMap_, - endpoint: 'verifyPhoneNumber', - requestValidator: fireauth.RpcHandler.validateVerifyPhoneNumberRequest_, - responseValidator: fireauth.RpcHandler.validateIdTokenResponse_, - requireTenantId: true - }, - WITHDRAW_MFA: { - endpoint: 'accounts/mfaEnrollment:withdraw', - requestRequiredFields: ['idToken', 'mfaEnrollmentId'], - responseValidator: fireauth.RpcHandler.validateWithdrawMfaResponse_, - requireTenantId: true, - useIdentityPlatformEndpoint: true - } -}; - - -/** - * @const {string} The parameter to send to the backend to specify that the - * client accepts STS tokens directly from Firebear backends. - * @private - */ -fireauth.RpcHandler.USE_STS_TOKEN_PARAM_ = 'returnSecureToken'; - - -/** - * @const {string} The parameter to send to the backend to specify the tenant - * ID. - * @private - */ -fireauth.RpcHandler.TENANT_ID_PARAM_ = 'tenantId'; - - -/** - * Invokes an RPC method according to the specification defined by - * {@code fireauth.RpcHandler.ApiMethod}. - * @param {!fireauth.RpcHandler.ApiMethod} method The method to invoke. - * @param {!Object} request The input data to the method. - * @return {!goog.Promise} A promise that resolves with the results of the RPC. - * The format of the results can be modified in - * {@code fireauth.RpcHandler.ApiMethod}. - */ -fireauth.RpcHandler.prototype.invokeRpc = function(method, request) { - if (!fireauth.object.hasNonEmptyFields( - request, method.requestRequiredFields)) { - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR)); - } - - var useIdentityPlatformEndpoint = !!method.useIdentityPlatformEndpoint; - var httpMethod = method.httpMethod || fireauth.RpcHandler.HttpMethod.POST; - var self = this; - var response; - return goog.Promise.resolve(request) - .then(method.requestValidator) - .then(function() { - if (method.returnSecureToken) { - // Signal that the client accepts STS tokens, for the legacy Google - // Identity Toolkit token to STS token migration. - request[fireauth.RpcHandler.USE_STS_TOKEN_PARAM_] = true; - } - // If tenant ID is explicitly passed in the request, it will override - // the tenant ID on RPC handler. - if (method.requireTenantId && self.tenantId_ && - (typeof request[fireauth.RpcHandler.TENANT_ID_PARAM_] === - 'undefined')) { - request[fireauth.RpcHandler.TENANT_ID_PARAM_] = self.tenantId_; - } - return useIdentityPlatformEndpoint ? - self.requestIdentityPlatformEndpoint(method.endpoint, httpMethod, - request, method.customErrorMap, method.cachebuster || false) : - self.requestFirebaseEndpoint(method.endpoint, httpMethod, - request, method.customErrorMap, method.cachebuster || false); - }) - .then(function(tempResponse) { - response = tempResponse; - // If response processor is available, pass request and response through - // it. Modifications would be made using response reference. - if (method.responsePreprocessor) { - return method.responsePreprocessor(request, response); - } - return response; - }) - .then(method.responseValidator) - .then(function() { - if (!method.responseField) { - return response; - } - if (!(method.responseField in response)) { - throw new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - } - return response[method.responseField]; - }); -}; - - -/** - * Checks if the server response contains errors. - * @param {!Object} resp The API response. - * @return {boolean} {@code true} if the response contains errors. - * @private - */ -fireauth.RpcHandler.hasError_ = function(resp) { - return !!resp['error']; -}; - - -/** - * Returns the developer facing error corresponding to the server code provided. - * @param {string} serverErrorCode The server error message. - * @return {!fireauth.AuthError} The corresponding error object. - * @private - */ -fireauth.RpcHandler.getDeveloperErrorFromCode_ = function(serverErrorCode) { - // Encapsulate the server error code in a typical server error response with - // the code populated within. This will convert the response to a developer - // facing one. - return fireauth.RpcHandler.getDeveloperError_({ - 'error': { - 'errors': [ - { - 'message': serverErrorCode - } - ], - 'code': 400, - 'message': serverErrorCode - } - }); -}; - - -/** - * Converts a server response with errors to a developer-facing AuthError. - * @param {!Object} response The server response. - * @param {?fireauth.RpcHandler.ServerErrorMap=} opt_customErrorMap A map of - * backend error codes to client-side errors. Any entries set here - * override the default handling of the backend error code. - * @return {!fireauth.AuthError} The corresponding error object. - * @private - */ -fireauth.RpcHandler.getDeveloperError_ = - function(response, opt_customErrorMap) { - var errorMessage; - var apiaryError = fireauth.RpcHandler.getApiaryError_(response); - if (apiaryError) { - return apiaryError; - } - - var serverErrorCode = fireauth.RpcHandler.getErrorCode_(response); - - var errorMap = {}; - - // Custom token errors. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CUSTOM_TOKEN] = - fireauth.authenum.Error.INVALID_CUSTOM_TOKEN; - errorMap[fireauth.RpcHandler.ServerError.CREDENTIAL_MISMATCH] = - fireauth.authenum.Error.CREDENTIAL_MISMATCH; - // This can only happen if the SDK sends a bad request. - errorMap[fireauth.RpcHandler.ServerError.MISSING_CUSTOM_TOKEN] = - fireauth.authenum.Error.INTERNAL_ERROR; - - // Create Auth URI errors. - errorMap[fireauth.RpcHandler.ServerError.INVALID_IDENTIFIER] = - fireauth.authenum.Error.INVALID_EMAIL; - // This can only happen if the SDK sends a bad request. - errorMap[fireauth.RpcHandler.ServerError.MISSING_CONTINUE_URI] = - fireauth.authenum.Error.INTERNAL_ERROR; - - // Sign in with email and password errors (some apply to sign up too). - errorMap[fireauth.RpcHandler.ServerError.INVALID_EMAIL] = - fireauth.authenum.Error.INVALID_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_PASSWORD] = - fireauth.authenum.Error.INVALID_PASSWORD; - errorMap[fireauth.RpcHandler.ServerError.USER_DISABLED] = - fireauth.authenum.Error.USER_DISABLED; - // This can only happen if the SDK sends a bad request. - errorMap[fireauth.RpcHandler.ServerError.MISSING_PASSWORD] = - fireauth.authenum.Error.INTERNAL_ERROR; - - // Sign up with email and password errors. - errorMap[fireauth.RpcHandler.ServerError.EMAIL_EXISTS] = - fireauth.authenum.Error.EMAIL_EXISTS; - errorMap[fireauth.RpcHandler.ServerError.PASSWORD_LOGIN_DISABLED] = - fireauth.authenum.Error.OPERATION_NOT_ALLOWED; - - // Verify assertion for sign in with credential errors: - errorMap[fireauth.RpcHandler.ServerError.INVALID_IDP_RESPONSE] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_PENDING_TOKEN] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.FEDERATED_USER_ID_ALREADY_LINKED] = - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_OR_INVALID_NONCE] = - fireauth.authenum.Error.MISSING_OR_INVALID_NONCE; - - // Email template errors while sending emails: - errorMap[fireauth.RpcHandler.ServerError.INVALID_MESSAGE_PAYLOAD] = - fireauth.authenum.Error.INVALID_MESSAGE_PAYLOAD; - errorMap[fireauth.RpcHandler.ServerError.INVALID_RECIPIENT_EMAIL] = - fireauth.authenum.Error.INVALID_RECIPIENT_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SENDER] = - fireauth.authenum.Error.INVALID_SENDER; - - // Send Password reset email errors: - errorMap[fireauth.RpcHandler.ServerError.EMAIL_NOT_FOUND] = - fireauth.authenum.Error.USER_DELETED; - errorMap[fireauth.RpcHandler.ServerError.RESET_PASSWORD_EXCEED_LIMIT] = - fireauth.authenum.Error.TOO_MANY_ATTEMPTS_TRY_LATER; - - // Reset password errors: - errorMap[fireauth.RpcHandler.ServerError.EXPIRED_OOB_CODE] = - fireauth.authenum.Error.EXPIRED_OOB_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_OOB_CODE] = - fireauth.authenum.Error.INVALID_OOB_CODE; - // This can only happen if the SDK sends a bad request. - errorMap[fireauth.RpcHandler.ServerError.MISSING_OOB_CODE] = - fireauth.authenum.Error.INTERNAL_ERROR; - - // Get Auth URI errors: - errorMap[fireauth.RpcHandler.ServerError.INVALID_PROVIDER_ID] = - fireauth.authenum.Error.INVALID_PROVIDER_ID; - - // Operations that require ID token in request: - errorMap[fireauth.RpcHandler.ServerError.CREDENTIAL_TOO_OLD_LOGIN_AGAIN] = - fireauth.authenum.Error.CREDENTIAL_TOO_OLD_LOGIN_AGAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_ID_TOKEN] = - fireauth.authenum.Error.INVALID_AUTH; - errorMap[fireauth.RpcHandler.ServerError.TOKEN_EXPIRED] = - fireauth.authenum.Error.TOKEN_EXPIRED; - errorMap[fireauth.RpcHandler.ServerError.USER_NOT_FOUND] = - fireauth.authenum.Error.TOKEN_EXPIRED; - - // CORS issues. - errorMap[fireauth.RpcHandler.ServerError.CORS_UNSUPPORTED] = - fireauth.authenum.Error.CORS_UNSUPPORTED; - - // Dynamic link not activated. - errorMap[fireauth.RpcHandler.ServerError.DYNAMIC_LINK_NOT_ACTIVATED] = - fireauth.authenum.Error.DYNAMIC_LINK_NOT_ACTIVATED; - - // iosBundleId or androidPackageName not valid error. - errorMap[fireauth.RpcHandler.ServerError.INVALID_APP_ID] = - fireauth.authenum.Error.INVALID_APP_ID; - - // Other errors. - errorMap[fireauth.RpcHandler.ServerError.TOO_MANY_ATTEMPTS_TRY_LATER] = - fireauth.authenum.Error.TOO_MANY_ATTEMPTS_TRY_LATER; - errorMap[fireauth.RpcHandler.ServerError.WEAK_PASSWORD] = - fireauth.authenum.Error.WEAK_PASSWORD; - errorMap[fireauth.RpcHandler.ServerError.OPERATION_NOT_ALLOWED] = - fireauth.authenum.Error.OPERATION_NOT_ALLOWED; - errorMap[fireauth.RpcHandler.ServerError.USER_CANCELLED] = - fireauth.authenum.Error.USER_CANCELLED; - - // Phone Auth related errors. - errorMap[fireauth.RpcHandler.ServerError.CAPTCHA_CHECK_FAILED] = - fireauth.authenum.Error.CAPTCHA_CHECK_FAILED; - errorMap[fireauth.RpcHandler.ServerError.INVALID_APP_CREDENTIAL] = - fireauth.authenum.Error.INVALID_APP_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_CODE] = - fireauth.authenum.Error.INVALID_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_PHONE_NUMBER] = - fireauth.authenum.Error.INVALID_PHONE_NUMBER; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SESSION_INFO] = - fireauth.authenum.Error.INVALID_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TEMPORARY_PROOF] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_APP_CREDENTIAL] = - fireauth.authenum.Error.MISSING_APP_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CODE] = - fireauth.authenum.Error.MISSING_CODE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_PHONE_NUMBER] = - fireauth.authenum.Error.MISSING_PHONE_NUMBER; - errorMap[fireauth.RpcHandler.ServerError.MISSING_SESSION_INFO] = - fireauth.authenum.Error.MISSING_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.QUOTA_EXCEEDED] = - fireauth.authenum.Error.QUOTA_EXCEEDED; - errorMap[fireauth.RpcHandler.ServerError.SESSION_EXPIRED] = - fireauth.authenum.Error.CODE_EXPIRED; - errorMap[fireauth.RpcHandler.ServerError.REJECTED_CREDENTIAL] = - fireauth.authenum.Error.REJECTED_CREDENTIAL; - - // Other action code errors when additional settings passed. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CONTINUE_URI] = - fireauth.authenum.Error.INVALID_CONTINUE_URI; - // MISSING_CONTINUE_URI is getting mapped to INTERNAL_ERROR above. - // This is OK as this error will be caught by client side validation. - errorMap[fireauth.RpcHandler.ServerError.MISSING_ANDROID_PACKAGE_NAME] = - fireauth.authenum.Error.MISSING_ANDROID_PACKAGE_NAME; - errorMap[fireauth.RpcHandler.ServerError.MISSING_IOS_BUNDLE_ID] = - fireauth.authenum.Error.MISSING_IOS_BUNDLE_ID; - errorMap[fireauth.RpcHandler.ServerError.UNAUTHORIZED_DOMAIN] = - fireauth.authenum.Error.UNAUTHORIZED_DOMAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_DYNAMIC_LINK_DOMAIN] = - fireauth.authenum.Error.INVALID_DYNAMIC_LINK_DOMAIN; - - // getProjectConfig errors when clientId is passed. - errorMap[fireauth.RpcHandler.ServerError.INVALID_OAUTH_CLIENT_ID] = - fireauth.authenum.Error.INVALID_OAUTH_CLIENT_ID; - // getProjectConfig errors when sha1Cert is passed. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CERT_HASH] = - fireauth.authenum.Error.INVALID_CERT_HASH; - - // Multi-tenant related errors. - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_TENANT_OPERATION] = - fireauth.authenum.Error.UNSUPPORTED_TENANT_OPERATION; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - errorMap[fireauth.RpcHandler.ServerError.TENANT_ID_MISMATCH] = - fireauth.authenum.Error.TENANT_ID_MISMATCH; - - // User actions (sign-up or deletion) disabled errors. - errorMap[fireauth.RpcHandler.ServerError.ADMIN_ONLY_OPERATION] = - fireauth.authenum.Error.ADMIN_ONLY_OPERATION; - - // Multi-factor related errors. - errorMap[fireauth.RpcHandler.ServerError.INVALID_MFA_PENDING_CREDENTIAL] = - fireauth.authenum.Error.INVALID_MFA_PENDING_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.MFA_ENROLLMENT_NOT_FOUND] = - fireauth.authenum.Error.MFA_ENROLLMENT_NOT_FOUND; - errorMap[fireauth.RpcHandler.ServerError.MISSING_MFA_PENDING_CREDENTIAL] = - fireauth.authenum.Error.MISSING_MFA_PENDING_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.MISSING_MFA_ENROLLMENT_ID] = - fireauth.authenum.Error.MISSING_MFA_ENROLLMENT_ID; - errorMap[fireauth.RpcHandler.ServerError.EMAIL_CHANGE_NEEDS_VERIFICATION] = - fireauth.authenum.Error.EMAIL_CHANGE_NEEDS_VERIFICATION; - errorMap[fireauth.RpcHandler.ServerError.SECOND_FACTOR_EXISTS] = - fireauth.authenum.Error.SECOND_FACTOR_EXISTS; - errorMap[fireauth.RpcHandler.ServerError.SECOND_FACTOR_LIMIT_EXCEEDED] = - fireauth.authenum.Error.SECOND_FACTOR_LIMIT_EXCEEDED; - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_FIRST_FACTOR] = - fireauth.authenum.Error.UNSUPPORTED_FIRST_FACTOR; - errorMap[fireauth.RpcHandler.ServerError.UNVERIFIED_EMAIL] = - fireauth.authenum.Error.UNVERIFIED_EMAIL; - - // Override errors set in the custom map. - var customErrorMap = opt_customErrorMap || {}; - goog.object.extend(errorMap, customErrorMap); - - // Get detailed message if available. - errorMessage = fireauth.RpcHandler.getErrorCodeDetails(serverErrorCode); - - // Handle backend errors where the error code can be a prefix of the message - // (e.g. "WEAK_PASSWORD : Password should be at least 6 characters"). - // Use the details after the colon as the error message. If none available, - // pass undefined, which will default to the client hard coded error messages. - for (var prefixCode in errorMap) { - if (serverErrorCode.indexOf(prefixCode) === 0) { - return new fireauth.AuthError(errorMap[prefixCode], errorMessage); - } - } - - // No error message found, return the serialized response as the message. - // This is likely to be an Apiary error for unexpected cases like keyExpired, - // etc. - if (!errorMessage && response) { - errorMessage = fireauth.util.stringifyJSON(response); - } - // The backend returned some error we don't recognize; this is an error on - // our side. - return new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, errorMessage); -}; - - -/** - * @param {string} serverMessage The server error code. - * @return {string|undefined} The detailed error code message. - */ -fireauth.RpcHandler.getErrorCodeDetails = function(serverMessage) { - // Use the error details part as the autherror message. - // For a message INVALID_CUSTOM_TOKEN : [error detail here], - // The Auth error message should be [error detail here]. - // No space should be contained in the error code, otherwise no detailed error - // message returned. - var matches = serverMessage.match(/^[^\s]+\s*:\s*([\s\S]*)$/); - if (matches && matches.length > 1) { - return matches[1]; - } - return undefined; -}; - - -/** - * Gets the Apiary error from a backend response, if applicable. - * @param {!Object} response The API response. - * @return {?fireauth.AuthError} The error, if applicable. - * @private - */ -fireauth.RpcHandler.getApiaryError_ = function(response) { - var error = response['error'] && response['error']['errors'] && - response['error']['errors'][0] || {}; - var reason = error['reason'] || ''; - - var errorReasonMap = { - 'keyInvalid': fireauth.authenum.Error.INVALID_API_KEY, - 'ipRefererBlocked': fireauth.authenum.Error.APP_NOT_AUTHORIZED - }; - - if (errorReasonMap[reason]) { - return new fireauth.AuthError(errorReasonMap[reason]); - } - - return null; -}; - - -/** - * Gets the server error code from the response. - * @param {!Object} resp The API response. - * @return {string} The error code if present. - * @private - */ -fireauth.RpcHandler.getErrorCode_ = function(resp) { - return (resp['error'] && resp['error']['message']) || ''; -}; diff --git a/packages/auth/src/storage/asyncstorage.js b/packages/auth/src/storage/asyncstorage.js deleted file mode 100644 index f1222e16cbc..00000000000 --- a/packages/auth/src/storage/asyncstorage.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.AsyncStorage'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.storage.Storage'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); - - -/** - * AsyncStorage provides an interface to the React Native AsyncStorage API. - * @param {!Object=} opt_asyncStorage The AsyncStorage API. If not provided - * this method will attempt to fetch an implementation from - * firebase.INTERNAL.reactNative. - * @constructor - * @implements {fireauth.storage.Storage} - * @see https://facebook.github.io/react-native/docs/asyncstorage.html - */ -fireauth.storage.AsyncStorage = function(opt_asyncStorage) { - /** - * The underlying storage instance for persistent data. - * @private - */ - this.storage_ = - opt_asyncStorage || (firebase.INTERNAL['reactNative'] && - firebase.INTERNAL['reactNative']['AsyncStorage']); - - if (!this.storage_) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'The React Native compatibility library was not found.'); - } - /** @public {string} The storage type identifier. */ - this.type = fireauth.storage.Storage.Type.ASYNC_STORAGE; -}; - - -/** - * Retrieves the value stored at the key. - * @param {string} key - * @return {!goog.Promise<*>} - * @override - */ -fireauth.storage.AsyncStorage.prototype.get = function(key) { - return goog.Promise.resolve(this.storage_['getItem'](key)) - .then(function(val) { - return val && fireauth.util.parseJSON(val); - }); -}; - - -/** - * Stores the value at the specified key. - * @param {string} key - * @param {*} value - * @return {!goog.Promise} - * @override - */ -fireauth.storage.AsyncStorage.prototype.set = function(key, value) { - return goog.Promise.resolve( - this.storage_['setItem'](key, fireauth.util.stringifyJSON(value))); -}; - - -/** - * Removes the value at the specified key. - * @param {string} key - * @return {!goog.Promise} - * @override - */ -fireauth.storage.AsyncStorage.prototype.remove = function(key) { - return goog.Promise.resolve(this.storage_['removeItem'](key)); -}; - - -/** - * Does nothing. AsyncStorage does not support storage events, - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.AsyncStorage.prototype.addStorageListener = function( - listener) {}; - - -/** - * Does nothing. AsyncStorage does not support storage events, - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.AsyncStorage.prototype.removeStorageListener = function( - listener) {}; diff --git a/packages/auth/src/storage/factory.js b/packages/auth/src/storage/factory.js deleted file mode 100644 index 3cc82b53765..00000000000 --- a/packages/auth/src/storage/factory.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.Factory'); -goog.provide('fireauth.storage.Factory.EnvConfig'); - -goog.require('fireauth.storage.AsyncStorage'); -goog.require('fireauth.storage.HybridIndexedDB'); -goog.require('fireauth.storage.InMemoryStorage'); -goog.require('fireauth.storage.LocalStorage'); -goog.require('fireauth.storage.NullStorage'); -goog.require('fireauth.storage.SessionStorage'); -goog.require('fireauth.util'); - - -/** - * Factory manages the storage implementations and determines the correct one - * for the current environment. - * @param {!fireauth.storage.Factory.EnvConfigType} env The storage - * configuration for the current environment. - * @constructor - */ -fireauth.storage.Factory = function(env) { - /** @const @private {!fireauth.storage.Factory.EnvConfigType} */ - this.env_ = env; -}; - - -/** - * Construct the singleton instance of the Factory, automatically detecting - * the current environment. - * @return {!fireauth.storage.Factory} - */ -fireauth.storage.Factory.getInstance = function() { - if (!fireauth.storage.Factory.instance_) { - fireauth.storage.Factory.instance_ = - new fireauth.storage.Factory(fireauth.storage.Factory.getEnvConfig()); - } - return fireauth.storage.Factory.instance_; -}; - - -/** - * @typedef {{ - * persistent: function(new:fireauth.storage.Storage), - * temporary: function(new:fireauth.storage.Storage) - * }} - */ -fireauth.storage.Factory.EnvConfigType; - - -/** - * Configurations of storage for different environments. - * @enum {!fireauth.storage.Factory.EnvConfigType} - */ -fireauth.storage.Factory.EnvConfig = { - BROWSER: { - persistent: fireauth.storage.LocalStorage, - temporary: fireauth.storage.SessionStorage - }, - NODE: { - persistent: fireauth.storage.LocalStorage, - temporary: fireauth.storage.SessionStorage - }, - REACT_NATIVE: { - persistent: fireauth.storage.AsyncStorage, - temporary: fireauth.storage.NullStorage - }, - WORKER: { - persistent: fireauth.storage.LocalStorage, - temporary: fireauth.storage.NullStorage - } -}; - - -/** - * Detects the current environment and returns the appropriate environment - * configuration. - * @return {!fireauth.storage.Factory.EnvConfigType} - */ -fireauth.storage.Factory.getEnvConfig = function() { - var envMap = {}; - envMap[fireauth.util.Env.BROWSER] = - fireauth.storage.Factory.EnvConfig.BROWSER; - envMap[fireauth.util.Env.NODE] = - fireauth.storage.Factory.EnvConfig.NODE; - envMap[fireauth.util.Env.REACT_NATIVE] = - fireauth.storage.Factory.EnvConfig.REACT_NATIVE; - envMap[fireauth.util.Env.WORKER] = - fireauth.storage.Factory.EnvConfig.WORKER; - return envMap[fireauth.util.getEnvironment()]; -}; - - -/** - * @return {!fireauth.storage.Storage} The persistent storage instance. - */ -fireauth.storage.Factory.prototype.makePersistentStorage = function() { - if (fireauth.util.persistsStorageWithIndexedDB()) { - // If persistent storage is implemented using indexedDB, use indexedDB. - // Use HybridIndexedDB instead of indexedDB directly since this will - // fallback to a fallback storage when indexedDB is not supported (private - // browsing mode, etc). - return new fireauth.storage.HybridIndexedDB( - fireauth.util.isWorker() ? - new fireauth.storage.InMemoryStorage() : new this.env_.persistent()); - } - return new this.env_.persistent(); -}; - - -/** - * @return {!fireauth.storage.Storage} The temporary storage instance. - */ -fireauth.storage.Factory.prototype.makeTemporaryStorage = function() { - return new this.env_.temporary(); -}; - - -/** - * @return {!fireauth.storage.Storage} An in memory storage instance. - */ -fireauth.storage.Factory.prototype.makeInMemoryStorage = function() { - return new fireauth.storage.InMemoryStorage(); -}; diff --git a/packages/auth/src/storage/hybridindexeddb.js b/packages/auth/src/storage/hybridindexeddb.js deleted file mode 100644 index fe590570a6f..00000000000 --- a/packages/auth/src/storage/hybridindexeddb.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.HybridIndexedDB'); - -goog.require('fireauth.storage.IndexedDB'); -goog.require('fireauth.storage.Storage'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.array'); - -/** - * HybridStorage provides an interface to indexedDB, the persistent Web - * Storage API for browsers that support it. This will fallback to the provided - * fallback storage when indexedDB is not supported which is determined - * asynchronously. - * @param {!fireauth.storage.Storage} fallbackStorage The storage to fallback to - * when indexedDB is not available. - * @constructor - * @implements {fireauth.storage.Storage} - */ -fireauth.storage.HybridIndexedDB = function(fallbackStorage) { - var self = this; - var storage = null; - /** - * @const @private {!Array))>} The storage listeners. - */ - this.storageListeners_ = []; - // This type may change if the fallback is used. - /** @public {string} The storage type identifier. */ - this.type = fireauth.storage.Storage.Type.INDEXEDDB; - /** - * @const @private {!fireauth.storage.Storage} The fallback storage when - * indexedDB is unavailable. - */ - this.fallbackStorage_ = fallbackStorage; - /** - * @const @private {!goog.Promise} A promise that - * resolves with the underlying indexedDB storage or a fallback when not - * supported. - */ - this.underlyingStoragePromise_ = goog.Promise.resolve().then(function() { - // Initial check shows indexedDB is available. This is not enough. - // Try to write/read from indexedDB. If it fails, switch to fallback. - if (fireauth.storage.IndexedDB.isAvailable()) { - // Test write/read using a random key. This is important for the following - // reasons: - // 1. Double inclusion of the firebase-auth.js library. - // 2. Multiple windows opened at the same time. - // The above may cause collision if multiple instances try to - // write/read/delete from the same entry. - var randomId = fireauth.util.generateEventId(); - var randomKey = fireauth.storage.HybridIndexedDB.KEY_ + randomId; - storage = fireauth.storage.IndexedDB.getFireauthManager(); - return storage.set(randomKey, randomId) - .then(function() { - return storage.get(randomKey); - }) - .then(function(value) { - if (value !== randomId) { - throw new Error('indexedDB not supported!'); - } - return storage.remove(randomKey); - }) - .then(function() { - return storage; - }) - .thenCatch(function(error) { - return self.fallbackStorage_; - }); - } else { - // indexedDB not available, use fallback. - return self.fallbackStorage_; - } - }).then(function(storage) { - // Update type. - self.type = storage.type; - // Listen to all storage changes. - storage.addStorageListener(function(key) { - // Trigger all attached storage listeners. - goog.array.forEach(self.storageListeners_, function(listener) { - listener(key); - }); - }); - return storage; - }); -}; - - -/** - * The key used to check if the storage instance is available. - * @private {string} - * @const - */ -fireauth.storage.HybridIndexedDB.KEY_ = '__sak'; - - -/** - * Retrieves the value stored at the key. - * @param {string} key - * @return {!goog.Promise<*>} - * @override - */ -fireauth.storage.HybridIndexedDB.prototype.get = function(key) { - return this.underlyingStoragePromise_.then(function(storage) { - return storage.get(key); - }); -}; - - -/** - * Stores the value at the specified key. - * @param {string} key - * @param {*} value - * @return {!goog.Promise} - * @override - */ -fireauth.storage.HybridIndexedDB.prototype.set = function(key, value) { - return this.underlyingStoragePromise_.then(function(storage) { - return storage.set(key, value); - }); -}; - - -/** - * Removes the value at the specified key. - * @param {string} key - * @return {!goog.Promise} - * @override - */ -fireauth.storage.HybridIndexedDB.prototype.remove = function(key) { - return this.underlyingStoragePromise_.then(function(storage) { - return storage.remove(key); - }); -}; - - -/** - * Adds a listener to storage event change. - * @param {function((!goog.events.BrowserEvent|!Array))} listener The - * storage event listener. - * @override - */ -fireauth.storage.HybridIndexedDB.prototype.addStorageListener = - function(listener) { - this.storageListeners_.push(listener); -}; - - -/** - * Removes a listener to storage event change. - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.HybridIndexedDB.prototype.removeStorageListener = - function(listener) { - goog.array.removeAllIf(this.storageListeners_, function(ele) { - return ele == listener; - }); -}; diff --git a/packages/auth/src/storage/indexeddb.js b/packages/auth/src/storage/indexeddb.js deleted file mode 100644 index 9633a9a139f..00000000000 --- a/packages/auth/src/storage/indexeddb.js +++ /dev/null @@ -1,733 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines a local storage interface with an indexedDB - * implementation to be used as a fallback with browsers that do not synchronize - * local storage changes between different windows of the same origin. - */ - -goog.provide('fireauth.storage.IndexedDB'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.messagechannel.Receiver'); -goog.require('fireauth.messagechannel.Sender'); -goog.require('fireauth.messagechannel.WorkerClientPostMessager'); -goog.require('fireauth.storage.Storage'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Timer'); -goog.require('goog.array'); - - - -/** - * Initialize an indexedDB local storage manager used to mimic local storage - * using an indexedDB underlying implementation including the ability to listen - * to storage changes by key similar to localstorage storage event. - * @param {string} dbName The indexedDB database name where all local storage - * data is to be stored. - * @param {string} objectStoreName The indexedDB object store name where all - * local storage data is to be stored. - * @param {string} dataKeyPath The indexedDB object store index name used to key - * all local storage data. - * @param {string} valueKeyPath The indexedDB object store value field for each - * entry. - * @param {number} version The indexedDB database version number. - * @param {?IDBFactory=} opt_indexedDB The optional IndexedDB factory object. - * @implements {fireauth.storage.Storage} - * @constructor - */ -fireauth.storage.IndexedDB = function( - dbName, - objectStoreName, - dataKeyPath, - valueKeyPath, - version, - opt_indexedDB) { - // indexedDB not available, fail hard. - if (!fireauth.storage.IndexedDB.isAvailable()) { - throw new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - } - /** - * @const @private {string} The indexedDB database name where all local - * storage data is to be stored. - */ - this.dbName_ = dbName; - /** - * @const @private {string} The indexedDB object store name where all local - * storage data is to be stored. - */ - this.objectStoreName_ = objectStoreName; - /** - * @const @private {string} The indexedDB object store index name used to key - * all local storage data. - */ - this.dataKeyPath_ = dataKeyPath; - /** - * @const @private {string} The indexedDB object store value field for each - * entry. - */ - this.valueKeyPath_ = valueKeyPath; - /** @const @private {number} The indexedDB database version number. */ - this.version_ = version; - /** @private {!Object.} The local indexedDB map copy. */ - this.localMap_ = {}; - /** - * @private {!Array)>} Listeners to storage events. - */ - this.storageListeners_ = []; - /** @private {number} The indexedDB pending write operations tracker. */ - this.pendingOpsTracker_ = 0; - /** @private {!IDBFactory} The indexedDB factory object. */ - this.indexedDB_ = /** @type {!IDBFactory} */ ( - opt_indexedDB || goog.global.indexedDB); - /** @public {string} The storage type identifier. */ - this.type = fireauth.storage.Storage.Type.INDEXEDDB; - /** - * @private {?goog.Promise} The pending polling promise for syncing - * unprocessed indexedDB external changes. - */ - this.poll_ = null; - /** - * @private {?number} The poll timer ID for syncing external indexedDB - * changes. - */ - this.pollTimerId_ = null; - /** - * @private {?fireauth.messagechannel.Receiver} The messageChannel receiver if - * running from a serviceworker. - */ - this.receiver_ = null; - /** - * @private {?fireauth.messagechannel.Sender} The messageChannel sender to - * send keyChanged messages to the service worker from the client. - */ - this.sender_ = null; - /** - * @private {boolean} Whether the service worker has a receiver for the - * keyChanged events. - */ - this.serviceWorkerReceiverAvailable_ = false; - /** @private {?ServiceWorker} The current active service worker. */ - this.activeServiceWorker_ = null; - var scope = this; - if (fireauth.util.getWorkerGlobalScope()) { - this.receiver_ = fireauth.messagechannel.Receiver.getInstance( - /** @type {!WorkerGlobalScope} */ ( - fireauth.util.getWorkerGlobalScope())); - // Listen to indexedDB changes. - this.receiver_.subscribe('keyChanged', function(origin, request) { - // Sync data. - return scope.sync_().then(function(keys) { - // Trigger listeners if unhandled changes are detected. - if (keys.length > 0) { - goog.array.forEach( - scope.storageListeners_, - function(listener) { - listener(keys); - }); - } - // When this is false, it means the change was already - // detected and processed before the notification. - return { - 'keyProcessed': goog.array.contains(keys, request['key']) - }; - }); - }); - // Used to inform sender that service worker what events it supports. - this.receiver_.subscribe('ping', function(origin, request) { - return goog.Promise.resolve(['keyChanged']); - }); - } else { - // Get active service worker when its available. - fireauth.util.getActiveServiceWorker() - .then(function(sw) { - scope.activeServiceWorker_ = sw; - if (sw) { - // Initialize the sender. - scope.sender_ = new fireauth.messagechannel.Sender( - new fireauth.messagechannel.WorkerClientPostMessager(sw)); - // Ping the service worker to check what events they can handle. - // Use long timeout. - scope.sender_.send('ping', null, true) - .then(function(results) { - // Check if keyChanged is supported. - if (results[0]['fulfilled'] && - goog.array.contains(results[0]['value'], 'keyChanged')) { - scope.serviceWorkerReceiverAvailable_ = true; - } - }) - .thenCatch(function(error) { - // Ignore error. - }); - } - }); - } -}; - - - -/** - * The indexedDB database name where all local storage data is to be stored. - * @private @const {string} - */ -fireauth.storage.IndexedDB.DB_NAME_ = 'firebaseLocalStorageDb'; - - -/** - * The indexedDB object store name where all local storage data is to be stored. - * @private @const {string} - */ -fireauth.storage.IndexedDB.DATA_OBJECT_STORE_NAME_ = 'firebaseLocalStorage'; - - -/** - * The indexedDB object store index name used to key all local storage data. - * @private @const {string} - */ -fireauth.storage.IndexedDB.DATA_KEY_PATH_ = 'fbase_key'; - - -/** - * The indexedDB object store value field for each entry. - * @private @const {string} - */ -fireauth.storage.IndexedDB.VALUE_KEY_PATH_ = 'value'; - - -/** - * The indexedDB database version number. - * @private @const {number} - */ -fireauth.storage.IndexedDB.VERSION_ = 1; - - -/** - * The indexedDB polling delay time in milliseconds. - * @private @const {number} - */ -fireauth.storage.IndexedDB.POLLING_DELAY_ = 800; - - -/** - * Maximum number of times to retry a transaction in the event the connection is - * closed. - * @private @const {number} - */ -fireauth.storage.IndexedDB.TRANSACTION_RETRY_COUNT_ = 3; - - -/** - * The indexedDB polling stop error. - * @private @const {string} - */ -fireauth.storage.IndexedDB.STOP_ERROR_ = 'STOP_EVENT'; - - - -/** - * @return {!fireauth.storage.IndexedDB} The Firebase Auth indexedDB - * local storage manager. - */ -fireauth.storage.IndexedDB.getFireauthManager = function() { - if (!fireauth.storage.IndexedDB.managerInstance_) { - fireauth.storage.IndexedDB.managerInstance_ = - new fireauth.storage.IndexedDB( - fireauth.storage.IndexedDB.DB_NAME_, - fireauth.storage.IndexedDB.DATA_OBJECT_STORE_NAME_, - fireauth.storage.IndexedDB.DATA_KEY_PATH_, - fireauth.storage.IndexedDB.VALUE_KEY_PATH_, - fireauth.storage.IndexedDB.VERSION_); - } - return fireauth.storage.IndexedDB.managerInstance_; -}; - - -/** - * Delete the indexedDB database. - * @return {!goog.Promise} A promise that resolves on successful - * database deletion. - * @private - */ -fireauth.storage.IndexedDB.prototype.deleteDb_ = function() { - var self = this; - return new goog.Promise(function(resolve, reject) { - var request = self.indexedDB_.deleteDatabase(self.dbName_); - request.onsuccess = function(event) { - resolve(); - }; - request.onerror = function(event) { - reject(new Error(event.target.error)); - }; - }); -}; - - -/** - * Initializes The indexedDB database, creates it if not already created and - * opens it. - * @return {!goog.Promise} A promise for the database object. - * @private - */ -fireauth.storage.IndexedDB.prototype.initializeDb_ = function() { - var self = this; - return new goog.Promise(function(resolve, reject) { - var request = self.indexedDB_.open(self.dbName_, self.version_); - request.onerror = function(event) { - // Suppress this from surfacing to browser console. - try { - event.preventDefault(); - } catch (e) {} - reject(new Error(event.target.error)); - }; - request.onupgradeneeded = function(event) { - var db = event.target.result; - try { - db.createObjectStore( - self.objectStoreName_, - { - 'keyPath': self.dataKeyPath_ - }); - } catch (e) { - reject(e); - } - }; - request.onsuccess = function(event) { - var db = event.target.result; - // Strange bug that occurs in Firefox when multiple tabs are opened at the - // same time. The only way to recover seems to be deleting the database - // and re-initializing it. - // https://github.com/firebase/firebase-js-sdk/issues/634 - if (!db.objectStoreNames.contains(self.objectStoreName_)) { - self.deleteDb_() - .then(function() { - return self.initializeDb_(); - }) - .then(function(newDb) { - resolve(newDb); - }) - .thenCatch(function(e) { - reject(e); - }); - } else { - resolve(db); - } - }; - }); -}; - - -/** - * Checks if indexedDB is initialized, if so, the callback is run, otherwise, - * it waits for the db to initialize and then runs the callback function. - * @return {!goog.Promise} A promise for the initialized indexedDB - * database. - * @private - */ -fireauth.storage.IndexedDB.prototype.initializeDbAndRun_ = - function() { - if (!this.initPromise_) { - this.initPromise_ = this.initializeDb_(); - } - return this.initPromise_; -}; - -/** -* Attempts to run a transaction, in the event of an error will re-initialize -* the DB connection and retry a fixed number of times. -* @param {function(!IDBDatabase): !goog.Promise<{T}>} transaction A method -* which performs a transactional operation on an IDBDatabase. -* @template T -* @return {!goog.Promise} -* @private -*/ -fireauth.storage.IndexedDB.prototype.withRetry_ = function(transaction) { - let numAttempts = 0; - const attempt = (resolve, reject) => { - this.initializeDbAndRun_() - .then(transaction) - .then(resolve) - .thenCatch((error) => { - if (++numAttempts > - fireauth.storage.IndexedDB.TRANSACTION_RETRY_COUNT_) { - reject(error); - return; - } - return this.initializeDbAndRun_().then((db) => { - db.close(); - this.initPromise_ = undefined; - return attempt(resolve, reject); - }).thenCatch((error) => { - // Make sure any errors caused by initializeDbAndRun_() or - // db.close() are caught as well and trigger a rejection. If at - // this point, we are probably in a private browsing context or - // environment that does not support indexedDB. - reject(error); - }); - }); - }; - return new goog.Promise(attempt); -} - - -/** - * @return {boolean} Whether indexedDB is available or not. - */ -fireauth.storage.IndexedDB.isAvailable = function() { - try { - return !!goog.global['indexedDB']; - } catch (e) { - return false; - } -}; - - -/** - * Creates a reference for the local storage indexedDB object store and returns - * it. - * @param {!IDBTransaction} tx The IDB transaction instance. - * @return {!IDBObjectStore} The indexedDB object store. - * @private - */ -fireauth.storage.IndexedDB.prototype.getDataObjectStore_ = - function(tx) { - return tx.objectStore(this.objectStoreName_); -}; - - -/** - * Creates an IDB transaction and returns it. - * @param {!IDBDatabase} db The indexedDB instance. - * @param {boolean} isReadWrite Whether the current indexedDB operation is a - * read/write operation or not. - * @return {!IDBTransaction} The requested IDB transaction instance. - * @private - */ -fireauth.storage.IndexedDB.prototype.getTransaction_ = - function(db, isReadWrite) { - var tx = db.transaction( - [this.objectStoreName_], - isReadWrite ? 'readwrite' : 'readonly'); - return tx; -}; - - -/** - * @param {!IDBRequest} request The IDB request instance. - * @return {!goog.Promise} The promise to resolve on transaction completion. - * @private - */ -fireauth.storage.IndexedDB.prototype.onIDBRequest_ = - function(request) { - return new goog.Promise(function(resolve, reject) { - request.onsuccess = function(event) { - if (event && event.target) { - resolve(event.target.result); - } else { - resolve(); - } - }; - request.onerror = function(event) { - reject(event.target.error); - }; - }); -}; - - -/** - * Sets the item's identified by the key provided to the value passed. If the - * item does not exist, it is created. An optional callback is run on success. - * @param {string} key The storage key for the item to set. If the item exists, - * it is updated, otherwise created. - * @param {*} value The value to store for the item to set. - * @return {!goog.Promise} A promise that resolves on operation success. - * @override - */ -fireauth.storage.IndexedDB.prototype.set = function(key, value) { - let isLocked = false; - return this - .withRetry_((db) => { - const objectStore = - this.getDataObjectStore_(this.getTransaction_(db, true)); - return this.onIDBRequest_(objectStore.get(key)); - }) - .then((data) => { - return this.withRetry_((db) => { - const objectStore = - this.getDataObjectStore_(this.getTransaction_(db, true)); - if (data) { - // Update the value(s) in the object that you want to change - data.value = value; - // Put this updated object back into the database. - return this.onIDBRequest_(objectStore.put(data)); - } - this.pendingOpsTracker_++; - isLocked = true; - const obj = {}; - obj[this.dataKeyPath_] = key; - obj[this.valueKeyPath_] = value; - return this.onIDBRequest_(objectStore.add(obj)); - }); - }) - .then(() => { - // Save in local copy to avoid triggering false external event. - this.localMap_[key] = value; - // Announce change in key to service worker. - return this.notifySW_(key); - }) - .thenAlways(() => { - if (isLocked) { - this.pendingOpsTracker_--; - } - }); -}; - - -/** - * Notify the service worker of the indexeDB write operation. - * Waits until the operation is processed. - * @param {string} key The key which is changing. - * @return {!goog.Promise} A promise that resolves on delivery. - * @private - */ -fireauth.storage.IndexedDB.prototype.notifySW_ = function(key) { - // If sender is available. - // Run some sanity check to confirm no sw change occurred. - // For now, we support one service worker per page. - if (this.sender_ && - this.activeServiceWorker_ && - fireauth.util.getServiceWorkerController() === - this.activeServiceWorker_) { - return this.sender_.send( - 'keyChanged', - {'key': key}, - // Use long timeout if receiver is known to be available. - this.serviceWorkerReceiverAvailable_) - .then(function(responses) { - // Return nothing. - }) - .thenCatch(function(error) { - // This is a best effort approach. Ignore errors. - }); - } - return goog.Promise.resolve(); -}; - - -/** - * Retrieves a stored item identified by the key provided asynchronously. - * The value is passed to the callback function provided. - * @param {string} key The storage key for the item to fetch. - * @return {!goog.Promise} A promise that resolves with the item's value, or - * null if the item is not found. - * @override - */ -fireauth.storage.IndexedDB.prototype.get = function(key) { - return this - .withRetry_((db) => { - return this.onIDBRequest_( - this.getDataObjectStore_(this.getTransaction_(db, false)).get(key)); - }) - .then((response) => { - return response && response.value; - }); -}; - - -/** - * Deletes the item identified by the key provided and on success, runs the - * optional callback. - * @param {string} key The storage key for the item to remove. - * @return {!goog.Promise} A promise that resolves on operation success. - * @override - */ -fireauth.storage.IndexedDB.prototype.remove = function(key) { - let isLocked = false; - return this - .withRetry_((db) => { - isLocked = true; - this.pendingOpsTracker_++; - return this.onIDBRequest_( - this.getDataObjectStore_( - this.getTransaction_(db, true))['delete'](key)); - }).then(() => { - // Delete from local copy to avoid triggering false external event. - delete this.localMap_[key]; - // Announce change in key to service worker. - return this.notifySW_(key); - }).thenAlways(() => { - if (isLocked) { - this.pendingOpsTracker_--; - } - }); -}; - - -/** - * @return {!goog.Promise>} A promise that resolved with all the - * storage keys that have changed. - * @private - */ -fireauth.storage.IndexedDB.prototype.sync_ = function() { - var self = this; - return this.initializeDbAndRun_() - .then(function(db) { - var objectStore = - self.getDataObjectStore_(self.getTransaction_(db, false)); - if (objectStore['getAll']) { - // Get all keys and value pairs using getAll if supported. - return self.onIDBRequest_(objectStore['getAll']()); - } else { - // If getAll isn't supported, fallback to cursor. - return new goog.Promise(function(resolve, reject) { - var res = []; - var request = objectStore.openCursor(); - request.onsuccess = function(event) { - var cursor = event.target.result; - if (cursor) { - res.push(cursor.value); - cursor['continue'](); - } else { - resolve(res); - } - }; - request.onerror = function(event) { - reject(event.target.error); - }; - }); - } - }).then(function(res) { - var centralCopy = {}; - // List of keys differing from central copy. - var diffKeys = []; - // Build central copy (external copy). - if (self.pendingOpsTracker_ == 0) { - for (var i = 0; i < res.length; i++) { - centralCopy[res[i][self.dataKeyPath_]] = - res[i][self.valueKeyPath_]; - } - // Get diff of central copy and local copy. - diffKeys = fireauth.util.getKeyDiff(self.localMap_, centralCopy); - // Update local copy. - self.localMap_ = centralCopy; - } - // Return modified keys. - return diffKeys; - }); -}; - - -/** - * Adds a listener to storage event change. - * @param {function(!Array)} listener The storage event listener. - * @override - */ -fireauth.storage.IndexedDB.prototype.addStorageListener = - function(listener) { - // First listener, start listeners. - if (this.storageListeners_.length == 0) { - this.startListeners_(); - } - this.storageListeners_.push(listener); -}; - - -/** - * Removes a listener to storage event change. - * @param {function(!Array)} listener The storage event listener. - * @override - */ -fireauth.storage.IndexedDB.prototype.removeStorageListener = - function(listener) { - goog.array.removeAllIf( - this.storageListeners_, - function(ele) { - return ele == listener; - }); - // No more listeners, stop. - if (this.storageListeners_.length == 0) { - this.stopListeners_(); - } -}; - - -/** - * Removes all listeners to storage event change. - */ -fireauth.storage.IndexedDB.prototype.removeAllStorageListeners = - function() { - this.storageListeners_ = []; - // No more listeners, stop. - this.stopListeners_(); -}; - - -/** - * Starts the listener to storage events. - * @private - */ -fireauth.storage.IndexedDB.prototype.startListeners_ = function() { - var self = this; - // Stop any previous listeners. - this.stopListeners_(); - var repeat = function() { - self.pollTimerId_ = setTimeout( - function() { - self.poll_ = self.sync_() - .then(function(keys) { - // If keys modified, call listeners. - if (keys.length > 0) { - goog.array.forEach( - self.storageListeners_, - function(listener) { - listener(keys); - }); - } - }) - .then(function() { - repeat(); - }) - .thenCatch(function(error) { - if (error.message != fireauth.storage.IndexedDB.STOP_ERROR_) { - repeat(); - } - }); - }, - fireauth.storage.IndexedDB.POLLING_DELAY_); - }; - repeat(); -}; - - -/** - * Stops the listener to storage events. - * @private - */ -fireauth.storage.IndexedDB.prototype.stopListeners_ = function() { - if (this.poll_) { - // Cancel polling function. - this.poll_.cancel(fireauth.storage.IndexedDB.STOP_ERROR_); - } - // Clear any pending polling timer. - if (this.pollTimerId_) { - clearTimeout(this.pollTimerId_); - this.pollTimerId_ = null; - } -}; diff --git a/packages/auth/src/storage/inmemorystorage.js b/packages/auth/src/storage/inmemorystorage.js deleted file mode 100644 index d9ba2e05264..00000000000 --- a/packages/auth/src/storage/inmemorystorage.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.InMemoryStorage'); - -goog.require('goog.Promise'); - - - -/** - * InMemoryStorage provides an implementation of Storage that will only persist - * data in memory. This data is volatile and in a browser environment, will - * be lost on page unload and will only be available in the current window. - * This is a useful fallback for browsers where web storage is disabled or - * environments where the preferred storage mechanism is not available or not - * supported. - * @constructor - * @implements {fireauth.storage.Storage} - */ -fireauth.storage.InMemoryStorage = function() { - /** @protected {!Object} The object where we store values. */ - this.storage = {}; - /** @public {string} The storage type identifier. */ - this.type = fireauth.storage.Storage.Type.IN_MEMORY; -}; - - -/** - * @param {string} key - * @return {!goog.Promise<*>} - * @override - */ -fireauth.storage.InMemoryStorage.prototype.get = function(key) { - return goog.Promise.resolve(/** @type {*} */ (this.storage[key])); -}; - - -/** - * @param {string} key - * @param {*} value - * @return {!goog.Promise} - * @override - */ -fireauth.storage.InMemoryStorage.prototype.set = function(key, value) { - this.storage[key] = value; - return goog.Promise.resolve(); -}; - - -/** - * @param {string} key - * @return {!goog.Promise} - * @override - */ -fireauth.storage.InMemoryStorage.prototype.remove = function(key) { - delete this.storage[key]; - return goog.Promise.resolve(); -}; - - -/** - * @param {function((!goog.events.BrowserEvent|!Array))} listener The - * storage event listener. - * @override - */ -fireauth.storage.InMemoryStorage.prototype.addStorageListener = - function(listener) { -}; - - -/** - * @param {function((!goog.events.BrowserEvent|!Array))} listener The - * storage event listener. - * @override - */ -fireauth.storage.InMemoryStorage.prototype.removeStorageListener = function( - listener) {}; diff --git a/packages/auth/src/storage/localstorage.js b/packages/auth/src/storage/localstorage.js deleted file mode 100644 index 3dae5f209b1..00000000000 --- a/packages/auth/src/storage/localstorage.js +++ /dev/null @@ -1,188 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.LocalStorage'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.storage.Storage'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.events'); - - - -/** - * LocalStorage provides an interface to localStorage, the persistent Web - * Storage API. - * @constructor - * @implements {fireauth.storage.Storage} - */ -fireauth.storage.LocalStorage = function() { - // Check is localStorage available in the current environment. - if (!fireauth.storage.LocalStorage.isAvailable()) { - // In a Node.js environment, dom-storage module needs to be required. - if (fireauth.util.getEnvironment() == fireauth.util.Env.NODE) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'The LocalStorage compatibility library was not found.'); - } - throw new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - } - - /** - * The underlying storage instance for persistent data. - * @private {!Storage} - */ - this.storage_ = /** @type {!Storage} */ ( - fireauth.storage.LocalStorage.getGlobalStorage() || - firebase.INTERNAL['node']['localStorage']); - /** @public {string} The storage type identifier. */ - this.type = fireauth.storage.Storage.Type.LOCAL_STORAGE; -}; - - -/** @return {?Storage|undefined} The global localStorage instance. */ -fireauth.storage.LocalStorage.getGlobalStorage = function() { - try { - var storage = goog.global['localStorage']; - // Try editing web storage. If an error is thrown, it may be disabled. - var key = fireauth.util.generateEventId(); - if (storage) { - storage['setItem'](key, '1'); - storage['removeItem'](key); - } - return storage; - } catch (e) { - // In some cases, browsers with web storage disabled throw an error simply - // on access. - return null; - } -}; - - -/** - * The key used to check if the storage instance is available. - * @private {string} - * @const - */ -fireauth.storage.LocalStorage.STORAGE_AVAILABLE_KEY_ = '__sak'; - - -/** @return {boolean} Whether localStorage is available. */ -fireauth.storage.LocalStorage.isAvailable = function() { - // In Node.js localStorage is polyfilled. - var isNode = fireauth.util.getEnvironment() == fireauth.util.Env.NODE; - // Either window should provide this storage mechanism or in case of Node.js, - // firebase.INTERNAL should provide it. - var storage = fireauth.storage.LocalStorage.getGlobalStorage() || - (isNode && - firebase.INTERNAL['node'] && - firebase.INTERNAL['node']['localStorage']); - if (!storage) { - return false; - } - try { - // setItem will throw an exception if we cannot access web storage (e.g., - // Safari in private mode). - storage.setItem(fireauth.storage.LocalStorage.STORAGE_AVAILABLE_KEY_, '1'); - storage.removeItem(fireauth.storage.LocalStorage.STORAGE_AVAILABLE_KEY_); - return true; - } catch (e) { - return false; - } -}; - - -/** - * Retrieves the value stored at the key. - * @param {string} key - * @return {!goog.Promise<*>} - * @override - */ -fireauth.storage.LocalStorage.prototype.get = function(key) { - var self = this; - return goog.Promise.resolve() - .then(function() { - var json = self.storage_.getItem(key); - return fireauth.util.parseJSON(json); - }); -}; - - -/** - * Stores the value at the specified key. - * @param {string} key - * @param {*} value - * @return {!goog.Promise} - * @override - */ -fireauth.storage.LocalStorage.prototype.set = function(key, value) { - var self = this; - return goog.Promise.resolve() - .then(function() { - var obj = fireauth.util.stringifyJSON(value); - if (obj === null) { - self.remove(key); - } else { - self.storage_.setItem(key, obj); - } - }); -}; - - -/** - * Removes the value at the specified key. - * @param {string} key - * @return {!goog.Promise} - * @override - */ -fireauth.storage.LocalStorage.prototype.remove = function(key) { - var self = this; - return goog.Promise.resolve() - .then(function() { - self.storage_.removeItem(key); - }); -}; - - -/** - * Adds a listener to storage event change. - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.LocalStorage.prototype.addStorageListener = function( - listener) { - if (goog.global['window']) { - goog.events.listen(goog.global['window'], 'storage', listener); - } -}; - - -/** - * Removes a listener to storage event change. - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.LocalStorage.prototype.removeStorageListener = function( - listener) { - if (goog.global['window']) { - goog.events.unlisten(goog.global['window'], 'storage', listener); - } -}; diff --git a/packages/auth/src/storage/mockstorage.js b/packages/auth/src/storage/mockstorage.js deleted file mode 100644 index 0e29ddb4766..00000000000 --- a/packages/auth/src/storage/mockstorage.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.MockStorage'); - -goog.require('fireauth.storage.InMemoryStorage'); -goog.require('fireauth.storage.Storage'); -goog.require('fireauth.util'); -goog.require('goog.array'); - - -/** - * Mock storage structure useful for testing and mocking local storage and other - * types of storage without depending on any native type of storage. - * @constructor - * @implements {fireauth.storage.Storage} - * @extends {fireauth.storage.InMemoryStorage} - */ -fireauth.storage.MockStorage = function() { - /** - * @private {!Array))>} The - * storage listeners. - */ - this.storageListeners_ = []; - fireauth.storage.MockStorage.base(this, 'constructor'); - /** @public {string} The storage type identifier. */ - this.type = fireauth.storage.Storage.Type.MOCK_STORAGE; -}; -goog.inherits(fireauth.storage.MockStorage, fireauth.storage.InMemoryStorage); - - -/** - * @param {function((!goog.events.BrowserEvent|!Array))} listener The - * storage event listener. - * @override - */ -fireauth.storage.MockStorage.prototype.addStorageListener = function(listener) { - this.storageListeners_.push(listener); -}; - - -/** - * @param {function((!goog.events.BrowserEvent|!Array))} listener The - * storage event listener. - * @override - */ -fireauth.storage.MockStorage.prototype.removeStorageListener = function( - listener) { - goog.array.removeAllIf(this.storageListeners_, function(ele) { - return ele == listener; - }); -}; - - -/** - * Simulates a storage event getting triggered which would trigger any attached - * listener. Any fired event would also update the underlying storage map. - * @param {!Event} storageEvent The storage event triggered. - */ -fireauth.storage.MockStorage.prototype.fireBrowserEvent = - function(storageEvent) { - // Get key of storage event. - var key = storageEvent.key; - if (key != null) { - // If key available, get newValue. - var newValue = storageEvent.newValue; - if (newValue != null) { - // newValue available, update corresponding value. - this.storage[key] = fireauth.util.parseJSON(newValue); - } else { - // newValue not available, delete the corresponding key's entry. - delete this.storage[key]; - } - } else { - // If key not available, clear storage. - this.clear(); - } - // Trigger all attached storage listeners. - goog.array.forEach(this.storageListeners_, function(listener) { - listener([key]); - }); -}; - - -/** Clears all stored data. */ -fireauth.storage.MockStorage.prototype.clear = function() { - this.storage = {}; -}; diff --git a/packages/auth/src/storage/nullstorage.js b/packages/auth/src/storage/nullstorage.js deleted file mode 100644 index 5a6da73b867..00000000000 --- a/packages/auth/src/storage/nullstorage.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.NullStorage'); - -goog.require('fireauth.storage.Storage'); -goog.require('goog.Promise'); - - - -/** - * NullStorage provides an implementation of Storage that does always returns - * null. This can be used if a type of storage is unsupported on a platform. - * @constructor - * @implements {fireauth.storage.Storage} - */ -fireauth.storage.NullStorage = function() { - /** @private {!Object} The object where we store values. */ - this.storage_ = {}; - /** @public {string} The storage type identifier. */ - this.type = fireauth.storage.Storage.Type.NULL_STORAGE; -}; - - -/** - * @param {string} key - * @return {!goog.Promise<*>} - * @override - */ -fireauth.storage.NullStorage.prototype.get = function(key) { - return goog.Promise.resolve(/** @type {*} */ (null)); -}; - - -/** - * @param {string} key - * @param {*} value - * @return {!goog.Promise} - * @override - */ -fireauth.storage.NullStorage.prototype.set = function(key, value) { - return goog.Promise.resolve(); -}; - - -/** - * @param {string} key - * @return {!goog.Promise} - * @override - */ -fireauth.storage.NullStorage.prototype.remove = function(key) { - return goog.Promise.resolve(); -}; - - -/** - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.NullStorage.prototype.addStorageListener = function(listener) { -}; - - -/** - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.NullStorage.prototype.removeStorageListener = function( - listener) {}; diff --git a/packages/auth/src/storage/sessionstorage.js b/packages/auth/src/storage/sessionstorage.js deleted file mode 100644 index 956beb2ac90..00000000000 --- a/packages/auth/src/storage/sessionstorage.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.SessionStorage'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.storage.Storage'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); - - - -/** - * SessionStorage provides an interface to sessionStorage, the temporary web - * storage API. - * @constructor - * @implements {fireauth.storage.Storage} - */ -fireauth.storage.SessionStorage = function() { - // Check is sessionStorage available in the current environment. - if (!fireauth.storage.SessionStorage.isAvailable()) { - // In a Node.js environment, dom-storage module needs to be required. - if (fireauth.util.getEnvironment() == fireauth.util.Env.NODE) { - throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'The SessionStorage compatibility library was not found.'); - } - throw new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - } - - /** - * The underlying storage instance for temporary data. - * @private {!Storage} - */ - this.storage_ = /** @type {!Storage} */ ( - fireauth.storage.SessionStorage.getGlobalStorage() || - firebase.INTERNAL['node']['sessionStorage']); - /** @public {string} The storage type identifier. */ - this.type = fireauth.storage.Storage.Type.SESSION_STORAGE; -}; - - -/** @return {?Storage|undefined} The global sessionStorage instance. */ -fireauth.storage.SessionStorage.getGlobalStorage = function() { - try { - var storage = goog.global['sessionStorage']; - // Try editing web storage. If an error is thrown, it may be disabled. - var key = fireauth.util.generateEventId(); - if (storage) { - storage['setItem'](key, '1'); - storage['removeItem'](key); - } - return storage; - } catch (e) { - // In some cases, browsers with web storage disabled throw an error simply - // on access. - return null; - } -}; - - -/** - * The key used to check if the storage instance is available. - * @private {string} - * @const - */ -fireauth.storage.SessionStorage.STORAGE_AVAILABLE_KEY_ = '__sak'; - - -/** @return {boolean} Whether sessionStorage is available. */ -fireauth.storage.SessionStorage.isAvailable = function() { - // In Node.js sessionStorage is polyfilled. - var isNode = fireauth.util.getEnvironment() == fireauth.util.Env.NODE; - // Either window should provide this storage mechanism or in case of Node.js, - // firebase.INTERNAL should provide it. - var storage = fireauth.storage.SessionStorage.getGlobalStorage() || - (isNode && - firebase.INTERNAL['node'] && - firebase.INTERNAL['node']['sessionStorage']); - if (!storage) { - return false; - } - try { - // setItem will throw an exception if we cannot access web storage (e.g., - // Safari in private mode). - storage.setItem( - fireauth.storage.SessionStorage.STORAGE_AVAILABLE_KEY_, '1'); - storage.removeItem(fireauth.storage.SessionStorage.STORAGE_AVAILABLE_KEY_); - return true; - } catch (e) { - return false; - } -}; - - -/** - * Retrieves the value stored at the key. - * @param {string} key - * @return {!goog.Promise<*>} - * @override - */ -fireauth.storage.SessionStorage.prototype.get = function(key) { - var self = this; - return goog.Promise.resolve() - .then(function() { - var json = self.storage_.getItem(key); - return fireauth.util.parseJSON(json); - }); -}; - - -/** - * Stores the value at the specified key. - * @param {string} key - * @param {*} value - * @return {!goog.Promise} - * @override - */ -fireauth.storage.SessionStorage.prototype.set = function(key, value) { - var self = this; - return goog.Promise.resolve() - .then(function() { - var obj = fireauth.util.stringifyJSON(value); - if (obj === null) { - self.remove(key); - } else { - self.storage_.setItem(key, obj); - } - }); -}; - - -/** - * Removes the value at the specified key. - * @param {string} key - * @return {!goog.Promise} - * @override - */ -fireauth.storage.SessionStorage.prototype.remove = function(key) { - var self = this; - return goog.Promise.resolve() - .then(function() { - self.storage_.removeItem(key); - }); -}; - - -/** - * Adds a listener to storage event change. - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.SessionStorage.prototype.addStorageListener = function( - listener) {}; - - -/** - * Removes a listener to storage event change. - * @param {function(!goog.events.BrowserEvent)} listener The storage event - * listener. - * @override - */ -fireauth.storage.SessionStorage.prototype.removeStorageListener = function( - listener) {}; diff --git a/packages/auth/src/storage/storage.js b/packages/auth/src/storage/storage.js deleted file mode 100644 index 8c02ed5a97a..00000000000 --- a/packages/auth/src/storage/storage.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.Storage'); - - - -/** - * Defines a generic interface to storage APIs across platforms. - * @interface - */ -fireauth.storage.Storage = function() {}; - - -/** - * Retrieves the value stored at the key. - * @param {string} key - * @return {!goog.Promise<*>} - */ -fireauth.storage.Storage.prototype.get = function(key) {}; - - -/** - * Stores the value at the specified key. - * @param {string} key - * @param {*} value - * @return {!goog.Promise} - */ -fireauth.storage.Storage.prototype.set = function(key, value) {}; - - -/** - * Removes the value at the specified key. - * @param {string} key - * @return {!goog.Promise} - */ -fireauth.storage.Storage.prototype.remove = function(key) {}; - - -/** - * Adds a listener to storage event change. - * @param {function(!goog.events.BrowserEvent)|function(!Array)} - * listener The storage event listener. - */ -fireauth.storage.Storage.prototype.addStorageListener = function(listener) {}; - - -/** - * Removes a listener to storage event change. - * @param {function(!goog.events.BrowserEvent)|function(!Array)} - * listener The storage event listener. - */ -fireauth.storage.Storage.prototype.removeStorageListener = function(listener) { -}; - - -/** @type {string} The storage type identifier. */ -fireauth.storage.Storage.prototype.type; - - -/** - * Enum for the identifier of the type of underlying storage. - * @enum {string} - */ -fireauth.storage.Storage.Type = { - ASYNC_STORAGE: 'asyncStorage', - IN_MEMORY: 'inMemory', - INDEXEDDB: 'indexedDB', - LOCAL_STORAGE: 'localStorage', - MOCK_STORAGE: 'mockStorage', - NULL_STORAGE: 'nullStorage', - SESSION_STORAGE: 'sessionStorage' -}; diff --git a/packages/auth/src/storageautheventmanager.js b/packages/auth/src/storageautheventmanager.js deleted file mode 100644 index b9425a30514..00000000000 --- a/packages/auth/src/storageautheventmanager.js +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the fireauth.storage.AuthEventManager class used by - * the iframe to retrieve and delete Auth events triggered through an OAuth - * flow. - */ - -goog.provide('fireauth.storage.AuthEventManager'); -goog.provide('fireauth.storage.AuthEventManager.Keys'); - -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.authStorage'); - - -/** - * Defines the Auth event storage manager. It provides methods to - * load and delete Auth events as well as listen to external OAuth changes on - * them. - * @param {string} appId The Auth event's application ID. - * @param {?fireauth.authStorage.Manager=} opt_manager The underlying storage - * manager to use. If none is provided, the default global instance is used. - * @constructor @struct @final - */ -fireauth.storage.AuthEventManager = function(appId, opt_manager) { - /** @const @private{string} appId The Auth event's application ID. */ - this.appId_ = appId; - /** - * @const @private{!fireauth.authStorage.Manager} The underlying storage - * manager. - */ - this.manager_ = opt_manager || fireauth.authStorage.Manager.getInstance(); -}; - - -/** - * Valid keys for Auth event manager data. - * @enum {!fireauth.authStorage.Key} - */ -fireauth.storage.AuthEventManager.Keys = { - AUTH_EVENT: { - name: 'authEvent', - persistent: fireauth.authStorage.Persistence.LOCAL - }, - REDIRECT_EVENT: { - name: 'redirectEvent', - persistent: fireauth.authStorage.Persistence.SESSION - } -}; - - -/** - * @return {!goog.Promise} A promise that resolves on - * success with the stored Auth event. - */ -fireauth.storage.AuthEventManager.prototype.getAuthEvent = function() { - return this.manager_.get( - fireauth.storage.AuthEventManager.Keys.AUTH_EVENT, this.appId_) - .then(function(response) { - return fireauth.AuthEvent.fromPlainObject(response); - }); -}; - - -/** - * Removes the identifier's Auth event if it exists. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.AuthEventManager.prototype.removeAuthEvent = function() { - return this.manager_.remove( - fireauth.storage.AuthEventManager.Keys.AUTH_EVENT, this.appId_); -}; - - -/** - * Adds a listener to Auth event for App ID provided. - * @param {!function()} listener The listener to run on Auth event. - */ -fireauth.storage.AuthEventManager.prototype.addAuthEventListener = - function(listener) { - this.manager_.addListener( - fireauth.storage.AuthEventManager.Keys.AUTH_EVENT, this.appId_, listener); -}; - - -/** - * Removes a listener to Auth event for App ID provided. - * @param {!function()} listener The listener to run on Auth event. - */ -fireauth.storage.AuthEventManager.prototype.removeAuthEventListener = - function(listener) { - this.manager_.removeListener( - fireauth.storage.AuthEventManager.Keys.AUTH_EVENT, this.appId_, listener); -}; - - -/** - * @return {!goog.Promise} A promise that resolves on - * success with the stored redirect Auth event. - */ -fireauth.storage.AuthEventManager.prototype.getRedirectEvent = - function() { - return this.manager_.get( - fireauth.storage.AuthEventManager.Keys.REDIRECT_EVENT, - this.appId_).then(function(response) { - return fireauth.AuthEvent.fromPlainObject(response); - }); -}; - - -/** - * Removes the identifier's redirect Auth event if it exists. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.AuthEventManager.prototype.removeRedirectEvent = function() { - return this.manager_.remove( - fireauth.storage.AuthEventManager.Keys.REDIRECT_EVENT, this.appId_); -}; diff --git a/packages/auth/src/storageoauthhandlermanager.js b/packages/auth/src/storageoauthhandlermanager.js deleted file mode 100644 index 167f15514a3..00000000000 --- a/packages/auth/src/storageoauthhandlermanager.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the fireauth.storage.OAuthHandlerManager class which - * provides utilities to the OAuth handler widget to set Auth events after an - * IDP sign in attempt and to store state during the OAuth handshake with IDP. - */ - -goog.provide('fireauth.storage.OAuthHandlerManager'); - -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.OAuthHelperState'); -goog.require('fireauth.authStorage'); -goog.require('fireauth.storage.AuthEventManager.Keys'); - - -/** - * Defines the OAuth handler storage manager. It provides methods to - * store, load and delete OAuth handler widget state, properties and setting - * Auth events. - * @param {?fireauth.authStorage.Manager=} opt_manager The underlying storage - * manager to use. If none is provided, the default global instance is used. - * @constructor @struct @final - */ -fireauth.storage.OAuthHandlerManager = function(opt_manager) { - /** - * @const @private{!fireauth.authStorage.Manager} The underlying storage - * manager. - */ - this.manager_ = opt_manager || fireauth.authStorage.Manager.getInstance(); -}; - - -/** - * Valid keys for OAuth handler manager data. - * @private @enum {!fireauth.authStorage.Key} - */ -fireauth.storage.OAuthHandlerManager.Keys_ = { - OAUTH_HELPER_STATE: { - name: 'oauthHelperState', - persistent: fireauth.authStorage.Persistence.SESSION - }, - SESSION_ID: { - name: 'sessionId', - persistent: fireauth.authStorage.Persistence.SESSION - } -}; - - -/** - * @param {string} appId The Auth state's application ID. - * @return {!goog.Promise} A promise that resolves on success - * with the stored session ID. - */ -fireauth.storage.OAuthHandlerManager.prototype.getSessionId = function(appId) { - return this.manager_.get( - fireauth.storage.OAuthHandlerManager.Keys_.SESSION_ID, appId); -}; - - -/** - * Removes the session ID string if it exists. - * @param {string} appId The Auth state's application ID. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.OAuthHandlerManager.prototype.removeSessionId = - function(appId) { - return this.manager_.remove( - fireauth.storage.OAuthHandlerManager.Keys_.SESSION_ID, appId); -}; - - -/** - * Stores the session ID string. - * @param {string} appId The Auth state's application ID. - * @param {string} sessionId The session ID string to store. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.OAuthHandlerManager.prototype.setSessionId = - function(appId, sessionId) { - return this.manager_.set( - fireauth.storage.OAuthHandlerManager.Keys_.SESSION_ID, sessionId, appId); -}; - - -/** - * @return {!goog.Promise} A promise that resolves - * on success with the stored OAuth helper state. - */ -fireauth.storage.OAuthHandlerManager.prototype.getOAuthHelperState = - function() { - return this.manager_.get( - fireauth.storage.OAuthHandlerManager.Keys_.OAUTH_HELPER_STATE) - .then(function(response) { - return fireauth.OAuthHelperState.fromPlainObject(response); - }); -}; - - -/** - * Removes the current OAuth helper state if it exists. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.OAuthHandlerManager.prototype.removeOAuthHelperState = - function() { - return this.manager_.remove( - fireauth.storage.OAuthHandlerManager.Keys_.OAUTH_HELPER_STATE); -}; - - -/** - * Stores the current OAuth helper state. - * @param {!fireauth.OAuthHelperState} state The OAuth helper state. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.OAuthHandlerManager.prototype.setOAuthHelperState = - function(state) { - return this.manager_.set( - fireauth.storage.OAuthHandlerManager.Keys_.OAUTH_HELPER_STATE, - state.toPlainObject()); -}; - - -/** - * Stores the Auth event for specified identifier. - * @param {string} appId The Auth state's application ID. - * @param {!fireauth.AuthEvent} authEvent The Auth event. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.OAuthHandlerManager.prototype.setAuthEvent = - function(appId, authEvent) { - return this.manager_.set( - fireauth.storage.AuthEventManager.Keys.AUTH_EVENT, - authEvent.toPlainObject(), - appId); -}; - - -/** - * Stores the redirect Auth event for specified identifier. - * @param {string} appId The Auth state's application ID. - * @param {!fireauth.AuthEvent} authEvent The redirect Auth event. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.OAuthHandlerManager.prototype.setRedirectEvent = - function(appId, authEvent) { - return this.manager_.set( - fireauth.storage.AuthEventManager.Keys.REDIRECT_EVENT, - authEvent.toPlainObject(), - appId); -}; diff --git a/packages/auth/src/storagependingredirectmanager.js b/packages/auth/src/storagependingredirectmanager.js deleted file mode 100644 index 7ba8aca7e0f..00000000000 --- a/packages/auth/src/storagependingredirectmanager.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the fireauth.storage.PendingRedirectManager class which - * provides utilities to store, retrieve and delete the state of whether there - * is a pending redirect operation previously triggered. - */ - -goog.provide('fireauth.storage.PendingRedirectManager'); - -goog.require('fireauth.authStorage'); - - -/** - * Defines the pending redirect storage manager. It provides methods - * to store, retrieve and delete the state of whether there is a pending - * redirect operation previously triggered. - * @param {string} appId The Auth state's application ID. - * @param {?fireauth.authStorage.Manager=} opt_manager The underlying storage - * manager to use. If none is provided, the default global instance is used. - * @constructor @struct @final - */ -fireauth.storage.PendingRedirectManager = function(appId, opt_manager) { - /** @const @private{string} appId The Auth state's application ID. */ - this.appId_ = appId; - /** - * @const @private{!fireauth.authStorage.Manager} The underlying storage - * manager. - */ - this.manager_ = opt_manager || fireauth.authStorage.Manager.getInstance(); -}; - - -/** - * @const @private{!string} The pending redirect flag. - */ -fireauth.storage.PendingRedirectManager.PENDING_FLAG_ = 'pending'; - - -/** - * @const @private{!fireauth.authStorage.Key} The pending redirect status - * storage identifier key. - */ -fireauth.storage.PendingRedirectManager.PENDING_REDIRECT_KEY_ = { - name: 'pendingRedirect', - persistent: fireauth.authStorage.Persistence.SESSION -}; - - -/** - * Stores the pending redirect operation for the provided application ID. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.PendingRedirectManager.prototype.setPendingStatus = - function() { - return this.manager_.set( - fireauth.storage.PendingRedirectManager.PENDING_REDIRECT_KEY_, - fireauth.storage.PendingRedirectManager.PENDING_FLAG_, - this.appId_); -}; - - -/** - * Removes the stored pending redirect operation for provided app ID. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.PendingRedirectManager.prototype.removePendingStatus = - function() { - return this.manager_.remove( - fireauth.storage.PendingRedirectManager.PENDING_REDIRECT_KEY_, - this.appId_); -}; - - -/** - * @return {!goog.Promise} A promise that resolves with a boolean - * whether there is a pending redirect operaiton for the provided app ID. - */ -fireauth.storage.PendingRedirectManager.prototype.getPendingStatus = - function() { - return this.manager_.get( - fireauth.storage.PendingRedirectManager.PENDING_REDIRECT_KEY_, - this.appId_).then(function(response) { - return response == - fireauth.storage.PendingRedirectManager.PENDING_FLAG_; - }); -}; diff --git a/packages/auth/src/storageredirectusermanager.js b/packages/auth/src/storageredirectusermanager.js deleted file mode 100644 index 52607c79b05..00000000000 --- a/packages/auth/src/storageredirectusermanager.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the fireauth.storage.RedirectUserManager class which - * provides utilities to store, retrieve and delete an Auth user during a - * redirect operation. - */ - -goog.provide('fireauth.storage.RedirectUserManager'); - -goog.require('fireauth.AuthUser'); -goog.require('fireauth.authStorage'); - - -/** - * Defines the Auth redirect user storage manager. It provides methods - * to store, load and delete a user going through a link with redirect - * operation. - * @param {string} appId The Auth state's application ID. - * @param {?fireauth.authStorage.Manager=} opt_manager The underlying storage - * manager to use. If none is provided, the default global instance is used. - * @constructor @struct @final - */ -fireauth.storage.RedirectUserManager = function(appId, opt_manager) { - /** @const @private{string} appId The Auth state's application ID. */ - this.appId_ = appId; - /** - * @const @private{!fireauth.authStorage.Manager} The underlying storage - * manager. - */ - this.manager_ = opt_manager || fireauth.authStorage.Manager.getInstance(); -}; - - -/** - * @const @private{!fireauth.authStorage.Key} The Auth redirect user storage - * identifier. - */ -fireauth.storage.RedirectUserManager.REDIRECT_USER_KEY_ = { - name: 'redirectUser', - persistent: fireauth.authStorage.Persistence.SESSION -}; - - -/** - * Stores the user being redirected for the provided application ID. - * @param {!fireauth.AuthUser} redirectUser The user being redirected. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.RedirectUserManager.prototype.setRedirectUser = - function(redirectUser) { - return this.manager_.set( - fireauth.storage.RedirectUserManager.REDIRECT_USER_KEY_, - redirectUser.toPlainObject(), - this.appId_); -}; - - -/** - * Removes the stored redirected user for provided app ID. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.RedirectUserManager.prototype.removeRedirectUser = - function() { - return this.manager_.remove( - fireauth.storage.RedirectUserManager.REDIRECT_USER_KEY_, this.appId_); -}; - - -/** - * @param {?string=} opt_authDomain The optional Auth domain to override if - * provided. - * @return {!goog.Promise} A promise that resolves with - * the stored redirected user for the provided app ID. - */ -fireauth.storage.RedirectUserManager.prototype.getRedirectUser = - function(opt_authDomain) { - return this.manager_.get( - fireauth.storage.RedirectUserManager.REDIRECT_USER_KEY_, this.appId_) - .then(function(response) { - // If potential user saved, override Auth domain if authDomain is - // provided. - if (response && opt_authDomain) { - response['authDomain'] = opt_authDomain; - } - return fireauth.AuthUser.fromPlainObject(response || {}); - }); -}; diff --git a/packages/auth/src/storageusermanager.js b/packages/auth/src/storageusermanager.js deleted file mode 100644 index a3bd0559a6b..00000000000 --- a/packages/auth/src/storageusermanager.js +++ /dev/null @@ -1,489 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines the fireauth.storage.UserManager class which provides - * utilities to retrieve, store and delete the currently logged in user and to - * listen to external authentication changes for the same app. - * With the ability to modify Auth state persistence. The behavior is as - * follows: - * Common cases: - *
    - *
  • Initially, local and session storage will be checked and the state will - * be loaded from there without changing it unless the developer calls - * setPersistence explicitly. The requirement is that at any time, Auth - * state can be saved using one type only of persistence and never more than - * one.
  • - *
  • If the developer tries to sign in with no persistence specified, the - * default setting will be used (local in a browser).
  • - *
  • If the user is not signed in and persistence is set, any future sign-in - * attempt will use that type of persistence.
  • - *
  • If the user is signed in and the developer then switches persistence, - * that existing signed in user will change persistence to the new one. All - * future sign-in attempts will use that same persistence.
  • - *
  • When signInWithRedirect is called, the current persistence type is passed - * along with that request and on redirect back to app will pass that type - * to determine how that state is saved (overriding the default). If the - * persistence is explicitly specified on that page, it will change that - * redirected Auth state persistence. This is the only time the persistence - * is passed from one page to another. - * So internally, on redirect, the redirect state is retrieved and then we - * check: If the persistence was explicitly provided, we override the - * previous type and save the Auth state using that. If no persistence was - * explicitly provided, we use the previous persistence type that was passed - * in the redirect response.
  • - *
- * Behavior across tabs: - *
    - *
  • User can sign in using session storage on multiple tabs. Each tab cannot - * see the state of the other tab.
  • - *
  • Any attempt to sign in using local storage will be detected and - * synchronized on all tabs. If the user was previously signed in on a - * specific tab using session storage, that state will be cleared.
  • - *
  • If the user was previously signed in using local storage and then signs - * in using session storage, the user will be signed in on the current tab - * only and signed out on all other tabs.
  • - *
  • Similar logic is applied to the ‘none’ state. In one tab, switching to - * ‘none’ state will delete any previously saved state in ‘local’ - * persistence in other tabs.
  • - *
- */ - -goog.provide('fireauth.storage.UserManager'); - -goog.require('fireauth.AuthUser'); -goog.require('fireauth.authStorage'); -goog.require('fireauth.constants'); -goog.require('goog.Promise'); - - -/** - * Defines the Auth user storage manager. It provides methods to - * store, load and delete an authenticated current user. It also provides - * methods to listen to external user changes (updates, sign in, sign out, etc.) - * @param {string} appId The Auth state's application ID. - * @param {?fireauth.authStorage.Manager=} opt_manager The underlying storage - * manager to use. If none is provided, the default global instance is used. - * @constructor @struct @final - */ -fireauth.storage.UserManager = function(appId, opt_manager) { - /** @const @private{string} appId The Auth state's application ID. */ - this.appId_ = appId; - /** - * @const @private{!fireauth.authStorage.Manager} The underlying storage - * manager. - */ - this.manager_ = opt_manager || fireauth.authStorage.Manager.getInstance(); - /** - * @private {?fireauth.authStorage.Key} The current Auth user storage - * identifier. - */ - this.currentAuthUserKey_ = null; - /** - * @private {!goog.Promise} Storage operation serializer promise. This will - * initialize the current persistence used and clean up any duplicate - * states or temporary values (persistence for pending redirect). - * Afterwards this is used to queue storage requests to make sure - * storage operations are always synchronized and read/write events are - * processed on the same storage. - */ - this.onReady_ = this.initialize_(); - // This internal listener will always run before the external ones. - // This is needed to queue processing of this first before any getCurrentUser - // is called from external listeners. - this.manager_.addListener( - fireauth.storage.UserManager.getAuthUserKey_( - fireauth.authStorage.Persistence.LOCAL), - this.appId_, - goog.bind(this.switchToLocalOnExternalEvent_, this)); -}; - - -/** - * Switches to local storage on external storage event. This will happen when - * state is specified as local in an external tab while it is none or session - * in the current one. If a user signs in in an external tab, the current window - * should detect this, clear existing storage and switch to local storage. - * @private - */ -fireauth.storage.UserManager.prototype.switchToLocalOnExternalEvent_ = - function() { - var self = this; - var localKey = fireauth.storage.UserManager.getAuthUserKey_( - fireauth.authStorage.Persistence.LOCAL); - // Wait for any pending operation to finish first. - // Block next read/write operation until persistence is transitioned to - // local. - this.waitForReady_(function() { - return goog.Promise.resolve().then(function() { - // If current persistence is not already local. - if (self.currentAuthUserKey_ && - self.currentAuthUserKey_.persistent != - fireauth.authStorage.Persistence.LOCAL) { - // Check if new current user is available in local storage. - return self.manager_.get(localKey, self.appId_); - } - return null; - }).then(function(response) { - // Sign in on an external tab. - if (response) { - // Remove any existing non-local user. - return self.removeAllExcept_( - fireauth.authStorage.Persistence.LOCAL).then(function() { - // Set persistence to local. - self.currentAuthUserKey_ = localKey; - }); - } - }); - }); -}; - - -/** - * Removes all states stored in all supported persistence types excluding the - * specified one. - * @param {?fireauth.authStorage.Persistence} persistence The type of storage - * persistence to switch to. - * @return {!goog.Promise} The promise that resolves when all stored values are - * removed for types of storage excluding specified persistence. This helps - * ensure there is always one type of persistence at any time. - * @private - */ -fireauth.storage.UserManager.prototype.removeAllExcept_ = - function(persistence) { - var promises = []; - // Queue all promises to remove current user in any other persistence type. - for (var key in fireauth.authStorage.Persistence) { - // Skip specified persistence. - if (fireauth.authStorage.Persistence[key] !== persistence) { - var storageKey = fireauth.storage.UserManager.getAuthUserKey_( - fireauth.authStorage.Persistence[key]); - promises.push(this.manager_.remove( - /** @type {!fireauth.authStorage.Key} */ (storageKey), - this.appId_)); - } - } - // Clear persistence key (only useful for initial load upon returning from a - // a redirect sign-in operation). - promises.push(this.manager_.remove( - fireauth.storage.UserManager.PERSISTENCE_KEY_, - this.appId_)); - return goog.Promise.all(promises); -}; - - -/** - * Initializes the current persistence state. This will check the 3 supported - * types. The first one that is found will be the current persistence. All - * others will be cleared. If none is found we check PERSISTENCE_KEY_ which - * when specified means that the operation is returning from a - * signInWithRedirect call. This persistence will be applied. - * Otherwise the default local persistence is used. - * @return {!goog.Promise} A promise that resolves when the current persistence - * is resolved. - * @private - */ -fireauth.storage.UserManager.prototype.initialize_ = function() { - var self = this; - // Local key. - var localKey = fireauth.storage.UserManager.getAuthUserKey_( - fireauth.authStorage.Persistence.LOCAL); - // Session key. - var sessionKey = fireauth.storage.UserManager.getAuthUserKey_( - fireauth.authStorage.Persistence.SESSION); - // In memory key. This is unlikely to contain anything on load. - var inMemoryKey = fireauth.storage.UserManager.getAuthUserKey_( - fireauth.authStorage.Persistence.NONE); - // Migrate any old currentUser from localStorage to indexedDB. - // This keeps any user signed in without the need for reauthentication and - // minimizes risks of dangling Auth states. - return this.manager_.migrateFromLocalStorage( - localKey, this.appId_).then(function() { - // Check if state is stored in session storage. - return self.manager_.get(sessionKey, self.appId_); - }).then(function(response) { - if (response) { - // Session storage is being used. - return sessionKey; - } else { - // Session storage is empty. Check in memory storage. - return self.manager_.get(inMemoryKey, self.appId_) - .then(function(response) { - if (response) { - // In memory storage being used. - return inMemoryKey; - } else { - // Check local storage. - return self.manager_.get(localKey, self.appId_) - .then(function(response) { - if (response) { - // Local storage being used. - return localKey; - } else { - // Nothing found in any supported storage. - // Check current user persistence in storage. - return self.manager_.get( - fireauth.storage.UserManager.PERSISTENCE_KEY_, - self.appId_).then(function(persistence) { - if (persistence) { - // Sign in with redirect operation, apply this - // persistence to any current user. - return fireauth.storage.UserManager - .getAuthUserKey_(persistence); - } else { - // No persistence found, use the default. - return localKey; - } - }); - } - }); - } - }); - } - }).then(function(currentKey) { - // Set current key according to the persistence detected. - self.currentAuthUserKey_ = currentKey; - // Make sure only one state available. Clean up everything else. - return self.removeAllExcept_(currentKey.persistent); - }).thenCatch(function(error) { - // If an error occurs in the process and no current key detected, set to - // persistence value to default. - if (!self.currentAuthUserKey_) { - self.currentAuthUserKey_ = localKey; - } - }); -}; - - -/** - * @const @private {string} The Auth current user storage identifier name. - */ -fireauth.storage.UserManager.AUTH_USER_KEY_NAME_ = 'authUser'; - - -/** - * @const @private{!fireauth.authStorage.Key} The Auth user storage persistence - * identifier. This is needed to remember the previous persistence state for - * sign-in with redirect. - */ -fireauth.storage.UserManager.PERSISTENCE_KEY_ = { - name: 'persistence', - persistent: fireauth.authStorage.Persistence.SESSION -}; - - -/** - * Returns the Auth user key corresponding to the persistence type provided. - * @param {!fireauth.authStorage.Persistence} persistence The key for the - * specified type of persistence. - * @return {!fireauth.authStorage.Key} The corresponding Auth user storage - * identifier. - * @private - */ -fireauth.storage.UserManager.getAuthUserKey_ = function(persistence) { - return { - name: fireauth.storage.UserManager.AUTH_USER_KEY_NAME_, - persistent: persistence - }; -}; - - -/** - * Sets the persistence to the specified type. - * If an existing user already is in storage, it copies that value to the new - * storage and clears all the others. - * @param {!fireauth.authStorage.Persistence} persistence The type of storage - * persistence to switch to. - * @return {!goog.Promise} A promise that resolves when persistence change is - * applied. - */ -fireauth.storage.UserManager.prototype.setPersistence = function(persistence) { - var currentUser = null; - var self = this; - // Validate the persistence type provided. This will throw a synchronous error - // if invalid. - fireauth.authStorage.validatePersistenceArgument(persistence); - // Wait for turn in queue. - return this.waitForReady_(function() { - // If persistence hasn't changed, do nothing. - if (persistence != self.currentAuthUserKey_.persistent) { - // Persistence changed. Copy from current storage to new one. - return self.manager_.get( - /** @type {!fireauth.authStorage.Key} */ (self.currentAuthUserKey_), - self.appId_).then(function(result) { - // Save current user. - currentUser = result; - // Clear from current storage. - return self.removeAllExcept_(persistence); - }).then(function() { - // Update persistence key to the new one. - self.currentAuthUserKey_ = - fireauth.storage.UserManager.getAuthUserKey_(persistence); - // Copy current storage type to the new one. - if (currentUser) { - return self.manager_.set( - /** @type {!fireauth.authStorage.Key} */ ( - self.currentAuthUserKey_), - currentUser, - self.appId_); - } - }); - } - // No change in persistence type. - return goog.Promise.resolve(); - }); -}; - - -/** - * Saves the current persistence type so it can be retrieved after a page - * redirect. This is relevant for signInWithRedirect. - * @return {!goog.Promise} Promise that resolve when current persistence is - * saved. - */ -fireauth.storage.UserManager.prototype.savePersistenceForRedirect = function() { - var self = this; - return this.waitForReady_(function() { - // Save persistence to survive redirect. - return self.manager_.set( - fireauth.storage.UserManager.PERSISTENCE_KEY_, - self.currentAuthUserKey_.persistent, - self.appId_); - }); -}; - - -/** - * Stores the current Auth user for the provided application ID. - * @param {!fireauth.AuthUser} currentUser The app current Auth user to save. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.UserManager.prototype.setCurrentUser = function(currentUser) { - var self = this; - // Wait for any pending persistence change to be resolved. - return this.waitForReady_(function() { - return self.manager_.set( - /** @type {!fireauth.authStorage.Key} */ (self.currentAuthUserKey_), - currentUser.toPlainObject(), - self.appId_); - }); -}; - - -/** - * Removes the stored current user for provided app ID. - * @return {!goog.Promise} A promise that resolves on success. - */ -fireauth.storage.UserManager.prototype.removeCurrentUser = function() { - var self = this; - // Wait for any pending persistence change to be resolved. - return this.waitForReady_(function() { - return self.manager_.remove( - /** @type {!fireauth.authStorage.Key} */ (self.currentAuthUserKey_), - self.appId_); - }); -}; - - -/** - * @param {?string=} authDomain The optional Auth domain to override if - * provided. - * @param {?fireauth.constants.EmulatorSettings=} emulatorConfig The current - * emulator config to use in user requests. - * @return {!goog.Promise} A promise that resolves with - * the stored current user for the provided app ID. - */ -fireauth.storage.UserManager.prototype.getCurrentUser = function(authDomain, emulatorConfig) { - var self = this; - // Wait for any pending persistence change to be resolved. - return this.waitForReady_(function() { - return self.manager_.get( - /** @type {!fireauth.authStorage.Key} */ (self.currentAuthUserKey_), - self.appId_).then(function(response) { - // If potential user saved, override Auth domain if authDomain is - // provided. - // This is useful in cases where on one page the developer initializes - // the Auth instance without authDomain and signs in user using - // headless methods. On another page, Auth is initialized with - // authDomain for the purpose of linking with a popup. The loaded user - // (stored without the authDomain) must have this field updated with - // the current authDomain. - if (response && authDomain) { - response['authDomain'] = authDomain; - } - if (response && emulatorConfig) { - response['emulatorConfig'] = emulatorConfig; - } - return fireauth.AuthUser.fromPlainObject(response || {}); - }); - }); -}; - - -/** - * Serializes storage access operations especially since persistence - * could be updated from one type to the other while read/write operations - * occur. - * @param {function():!goog.Promise} cb The promise return callback to chain - * when pending operations are resolved. - * @return {!goog.Promise} The resulting promise that resolves when provided - * promise finally resolves. - * @template T - * @private - */ -fireauth.storage.UserManager.prototype.waitForReady_ = function(cb) { - // Wait for any pending persistence change to be resolved before running - // storage related operation. Chain to onReady so next call will wait for - // this operation to resolve. - // While an error is unlikely, run callback even if it happens, otherwise - // no storage related event will be allowed to complete after an error. - this.onReady_ = this.onReady_.then(cb, cb); - return this.onReady_; -}; - - -/** - * Adds a listener to Auth current user change event for app ID provided. - * @param {!function()} listener The listener to run on current user change - * event. - */ -fireauth.storage.UserManager.prototype.addCurrentUserChangeListener = - function(listener) { - // When this is triggered, getCurrentUser is called, that will have to wait - // for switchToLocalOnExternalEvent_ to resolve which is ahead of it in the - // queue. - this.manager_.addListener( - fireauth.storage.UserManager.getAuthUserKey_( - fireauth.authStorage.Persistence.LOCAL), - this.appId_, - listener); -}; - - -/** - * Removes a listener to Auth current user change event for app ID provided. - * @param {!function()} listener The listener to remove from current user change - * event changes. - */ -fireauth.storage.UserManager.prototype.removeCurrentUserChangeListener = - function(listener) { - this.manager_.removeListener( - fireauth.storage.UserManager.getAuthUserKey_( - fireauth.authStorage.Persistence.LOCAL), - this.appId_, - listener); -}; diff --git a/packages/auth/src/token.js b/packages/auth/src/token.js deleted file mode 100644 index 9cfbb703d55..00000000000 --- a/packages/auth/src/token.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Utility class to retrieve and cache STS token. - */ -goog.provide('fireauth.StsTokenManager'); -goog.provide('fireauth.StsTokenManager.Response'); -goog.provide('fireauth.StsTokenManager.ResponseData'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.IdToken'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.authenum.Error'); -goog.require('goog.Promise'); -goog.require('goog.asserts'); - - - -/** - * Creates STS token manager. - * - * @param {!fireauth.RpcHandler} rpcHandler Handler for RPC requests. - * @constructor - */ -fireauth.StsTokenManager = function(rpcHandler) { - /** - * @const @private {!fireauth.RpcHandler} The RPC handler used to request STS - * tokens. - */ - this.rpcHandler_ = rpcHandler; - /** @private {?string} The STS refresh token. */ - this.refreshToken_ = null; - /** @private {?fireauth.IdToken} The STS ID token. */ - this.accessToken_ = null; - /** @private {number} The expiration time of the token in epoch millis. */ - this.expiresAt_ = Date.now(); -}; - - -/** - * @return {!Object} The plain object representation of the STS token manager. - */ -fireauth.StsTokenManager.prototype.toPlainObject = function() { - return { - 'apiKey': this.rpcHandler_.getApiKey(), - 'refreshToken': this.refreshToken_, - 'accessToken': this.accessToken_ && this.accessToken_.toString(), - // To support downgrade flows, return expiration time. - 'expirationTime': this.getExpirationTime() - }; -}; - - -/** - * @param {!fireauth.RpcHandler} rpcHandler The RPC handler for the token - * manager. - * @param {?Object} obj The plain object whose STS token manager instance is to - * be returned. - * @return {?fireauth.StsTokenManager} The STS token manager instance from the - * plain object provided using the RPC handler provided. - */ -fireauth.StsTokenManager.fromPlainObject = function(rpcHandler, obj) { - let stsTokenManager = null; - if (obj && obj['apiKey']) { - // These should be always equals and must be enforced in internal use. - goog.asserts.assert(obj['apiKey'] == rpcHandler.getApiKey()); - stsTokenManager = new fireauth.StsTokenManager(rpcHandler); - stsTokenManager.setRefreshToken(obj['refreshToken']); - stsTokenManager.setAccessToken(obj['accessToken']); - stsTokenManager.setExpiresAt(obj['expirationTime']); - } - return stsTokenManager; -}; - - -/** - * @typedef {{ - * accessToken: (?string), - * refreshToken: (?string) - * }} - */ -fireauth.StsTokenManager.Response; - - -/** - * @typedef {{ - * access_token: (?string|undefined), - * refresh_token: (?string|undefined) - * }} - */ -fireauth.StsTokenManager.ResponseData; - - -/** - * @param {?string} refreshToken The STS refresh token. - */ -fireauth.StsTokenManager.prototype.setRefreshToken = function(refreshToken) { - this.refreshToken_ = refreshToken; -}; - - -/** - * @param {?string} accessToken The STS access token. - */ -fireauth.StsTokenManager.prototype.setAccessToken = function(accessToken) { - this.accessToken_ = fireauth.IdToken.parse(accessToken || ''); -}; - -/** - * To account for client side clock skew, we try to set the expiration time - * using the local clock by adding the server TTL. If not provided, expiresAt - * will be set from the accessToken by taking the difference between the exp - * and iat fields. - * - * @param {number=} expiresIn The expiration TTL in seconds. - */ -fireauth.StsTokenManager.prototype.setExpiresIn = function(expiresIn) { - expiresIn = typeof expiresIn !== 'undefined' ? expiresIn : - this.accessToken_ ? this.accessToken_.getExpiresIn() : - 0; - this.expiresAt_ = Date.now() + expiresIn * 1000; -} - -/** - * Allow setting expiresAt directly when we know the time is already in the - * local clock. - * - * @param {number} expiresAt The expiration time in epoch millis. - */ -fireauth.StsTokenManager.prototype.setExpiresAt = function(expiresAt) { - this.expiresAt_ = expiresAt; -} - -/** - * @return {?string} The refresh token. - */ -fireauth.StsTokenManager.prototype.getRefreshToken = function() { - return this.refreshToken_; -}; - - -/** - * @return {number} The STS access token expiration time in milliseconds. - */ -fireauth.StsTokenManager.prototype.getExpirationTime = function() { - return this.expiresAt_; -}; - - -/** - * The number of milliseconds before the official expiration time of a token - * to refresh that token, to provide a buffer for RPCs to complete. - * @const {number} - */ -fireauth.StsTokenManager.TOKEN_REFRESH_BUFFER = 30 * 1000; - - -/** - * @return {boolean} Whether the STS access token is expired or not. - * @private - */ -fireauth.StsTokenManager.prototype.isExpired_ = function() { - return Date.now() > - this.getExpirationTime() - fireauth.StsTokenManager.TOKEN_REFRESH_BUFFER; -}; - - -/** - * Parses a response from the server that contains STS tokens (e.g. from - * VerifyAssertion or VerifyPassword) and save the access token, refresh token, - * and expiration time. - * @param {!Object} response The backend response. - * @return {string} The STS access token. - */ -fireauth.StsTokenManager.prototype.parseServerResponse = function(response) { - const idToken = response[fireauth.RpcHandler.AuthServerField.ID_TOKEN]; - this.setAccessToken(idToken); - this.setRefreshToken( - response[fireauth.RpcHandler.AuthServerField.REFRESH_TOKEN]); - // Not all IDP server responses come with expiresIn, some like MFA omit it. - const expiresIn = response[fireauth.RpcHandler.AuthServerField.EXPIRES_IN]; - this.setExpiresIn( - typeof expiresIn !== 'undefined' ? Number(expiresIn) : undefined); - return idToken; -}; - - -/** - * Converts STS token manager instance to server response object. - * @return {!Object} - */ -fireauth.StsTokenManager.prototype.toServerResponse = function() { - const stsTokenManagerResponse = {}; - stsTokenManagerResponse[fireauth.RpcHandler.AuthServerField.ID_TOKEN] = - this.accessToken_ && this.accessToken_.toString(); - // Refresh token could be expired. - stsTokenManagerResponse[fireauth.RpcHandler.AuthServerField.REFRESH_TOKEN] = - this.getRefreshToken(); - return stsTokenManagerResponse; -}; - - -/** - * Copies IdToken, refreshToken and expirationTime from tokenManagerToCopy. - * @param {!fireauth.StsTokenManager} tokenManagerToCopy - */ -fireauth.StsTokenManager.prototype.copy = function(tokenManagerToCopy) { - this.accessToken_ = tokenManagerToCopy.accessToken_; - this.refreshToken_ = tokenManagerToCopy.refreshToken_; - this.expiresAt_ = tokenManagerToCopy.expiresAt_; -}; - - -/** - * Exchanges the current refresh token with an access and refresh token. - * @return {!goog.Promise} - * @private - */ -fireauth.StsTokenManager.prototype.exchangeRefreshToken_ = function() { - const data = { - 'grant_type': 'refresh_token', - 'refresh_token': this.refreshToken_ - }; - return this.requestToken_(data); -}; - - -/** - * Sends a request to STS token endpoint for an access/refresh token. - * @param {!Object} data The request data to send to STS token endpoint. - * @return {!goog.Promise} - * @private - */ -fireauth.StsTokenManager.prototype.requestToken_ = function(data) { - // Send RPC request to STS token endpoint. - return this.rpcHandler_.requestStsToken(data) - .then((resp) => { - const response = - /** @type {!fireauth.StsTokenManager.ResponseData} */ (resp); - this.accessToken_ = fireauth.IdToken.parse( - response[fireauth.RpcHandler.StsServerField.ACCESS_TOKEN]); - this.refreshToken_ = - response[fireauth.RpcHandler.StsServerField.REFRESH_TOKEN]; - this.setExpiresIn( - response[fireauth.RpcHandler.StsServerField.EXPIRES_IN]); - return /** @type {!fireauth.StsTokenManager.Response} */ ({ - 'accessToken': this.accessToken_.toString(), - 'refreshToken': this.refreshToken_ - }); - }) - .thenCatch((error) => { - // Refresh token expired or user deleted. In this case, reset refresh - // token to prevent sending the request again to the STS server unless - // the token is manually updated, perhaps via successful - // reauthentication. - if (error['code'] == 'auth/user-token-expired') { - this.refreshToken_ = null; - } - throw error; - }); -}; - - -/** @return {boolean} Whether the refresh token is expired. */ -fireauth.StsTokenManager.prototype.isRefreshTokenExpired = function() { - return !!(this.accessToken_ && !this.refreshToken_); -}; - - -/** - * Returns an STS token. If the cached one is unexpired it is directly returned. - * Otherwise the existing ID token or refresh token is exchanged for a new one. - * If there is no user signed in, returns null. - * - * @param {boolean=} forceRefresh Whether to force refresh token exchange. - * @return {!goog.Promise} - */ -fireauth.StsTokenManager.prototype.getToken = function(forceRefresh) { - forceRefresh = !!forceRefresh; - // Refresh token is expired. - if (this.isRefreshTokenExpired()) { - return goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED)); - } - if (!forceRefresh && this.accessToken_ && !this.isExpired_()) { - // Cached STS access token not expired, return it. - return /** @type {!goog.Promise} */ (goog.Promise.resolve({ - 'accessToken': this.accessToken_.toString(), - 'refreshToken': this.refreshToken_ - })); - } else if (this.refreshToken_) { - // Expired but refresh token available, exchange refresh token for STS - // token. - return this.exchangeRefreshToken_(); - } else { - // No token, return null token. - return goog.Promise.resolve( - /** @type {?fireauth.StsTokenManager.Response} */ (null)); - } -}; diff --git a/packages/auth/src/universallinksubscriber.js b/packages/auth/src/universallinksubscriber.js deleted file mode 100644 index a10790b3f67..00000000000 --- a/packages/auth/src/universallinksubscriber.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Provides the universal link subscriber utility to allow - * multiple subscriptions for incoming universal link detection. - */ -goog.provide('fireauth.UniversalLinkSubscriber'); - -goog.require('fireauth.util'); -goog.require('goog.array'); - -/** - * Defines the universal link subscriber class used to allow multiple universal - * link subscriptions since the underlying plugin only works with one. - * This utility is needed since the universal link cordova plugin can only allow - * one subscriber and multiple app instances can subscribe to this. - * @constructor @final @struct - */ -fireauth.UniversalLinkSubscriber = function() { - /** - * @private {?function(?Object)} The master callback that subscribes directly - * to universalLinks. - */ - this.masterCb_ = null; - /** - * @private {!Array} The list of external subscribers that - * are triggered every time the master callback is triggered. - */ - this.cb_ = []; -}; - - -/** - * @return {!fireauth.UniversalLinkSubscriber} The default universal link - * subscriber instance. - */ -fireauth.UniversalLinkSubscriber.getInstance = function() { - if (!fireauth.UniversalLinkSubscriber.instance_) { - fireauth.UniversalLinkSubscriber.instance_ = - new fireauth.UniversalLinkSubscriber(); - } - return fireauth.UniversalLinkSubscriber.instance_; -}; - - -/** Clears singleton instance. Useful for testing. */ -fireauth.UniversalLinkSubscriber.clear = function() { - fireauth.UniversalLinkSubscriber.instance_ = null; -}; - - -/** - * @private {?fireauth.UniversalLinkSubscriber} The singleton universal - * link subscriber instance. - */ -fireauth.UniversalLinkSubscriber.instance_ = null; - - -/** - * Subscribes a callback to the universal link plugin listener. - * @param {function(?Object)} cb The callback to subscribe to the universal - * link plugin. - */ -fireauth.UniversalLinkSubscriber.prototype.subscribe = function(cb) { - var self = this; - this.cb_.push(cb); - if (!this.masterCb_) { - this.masterCb_ = function(event) { - for (var i = 0; i < self.cb_.length; i++) { - self.cb_[i](event); - } - }; - var subscribe = fireauth.util.getObjectRef( - 'universalLinks.subscribe', goog.global); - // For iOS environments, this plugin is not used, therefore this is a no-op - // and no error needs to be thrown. - if (typeof subscribe === 'function') { - subscribe(null, this.masterCb_); - } - } -}; - - -/** - * Unsubscribes a callback from the universal link plugin listener. - * @param {function(?Object)} cb The callback to unsubscribe from the universal - * link plugin. - */ -fireauth.UniversalLinkSubscriber.prototype.unsubscribe = function(cb) { - goog.array.removeAllIf(this.cb_, function(ele) { - return ele == cb; - }); -}; - diff --git a/packages/auth/src/userevent.js b/packages/auth/src/userevent.js deleted file mode 100644 index 8d70f3f1a06..00000000000 --- a/packages/auth/src/userevent.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Defines fireauth.UserEvent and fireauth.UserEventType. - */ - -goog.provide('fireauth.UserEvent'); -goog.provide('fireauth.UserEventType'); - -goog.require('goog.events'); -goog.require('goog.events.Event'); - - -/** - * User custom event. - * @param {string} type The event type. - * @param {?Object=} properties The optional properties to set to the custom - * event using same keys as object provided. - * @constructor - * @extends {goog.events.Event} - */ -fireauth.UserEvent = function(type, properties) { - goog.events.Event.call(this, type); - // If optional properties provided. - // Add each property to custom event. - for (var key in properties) { - this[key] = properties[key]; - } -}; -goog.inherits(fireauth.UserEvent, goog.events.Event); - - -/** - * Events dispatched by the user. - * @enum {string} - */ -fireauth.UserEventType = { - /** Dispatched when token is changed due to Auth event. */ - TOKEN_CHANGED: 'tokenChanged', - - /** Dispatched when user is deleted. */ - USER_DELETED: 'userDeleted', - - /** - * Dispatched when user session is invalidated. This could happen when the - * following errors occur: user-disabled or user-token-expired. - */ - USER_INVALIDATED: 'userInvalidated', - - /** Dispatched when the user is reloaded. */ - USER_RELOADED: 'userReloaded' -}; diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js deleted file mode 100644 index 7480d251830..00000000000 --- a/packages/auth/src/utils.js +++ /dev/null @@ -1,1528 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Defines utility and helper functions. - */ - -goog.provide('fireauth.util'); - -goog.require('goog.Promise'); -goog.require('goog.Timer'); -goog.require('goog.Uri'); -goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.html.SafeUrl'); -goog.require('goog.json'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('goog.userAgent'); -goog.require('goog.window'); - - -/** @suppress {duplicate} Suppress variable 'angular' first declared. */ -var angular; - -/** - * Checks whether the user agent is IE11. - * @return {boolean} True if it is IE11. - */ -fireauth.util.isIe11 = function() { - return goog.userAgent.IE && - !!goog.userAgent.DOCUMENT_MODE && - goog.userAgent.DOCUMENT_MODE == 11; -}; - - -/** - * Checks whether the user agent is IE10. - * @return {boolean} True if it is IE10. - */ -fireauth.util.isIe10 = function() { - return goog.userAgent.IE && - !!goog.userAgent.DOCUMENT_MODE && - goog.userAgent.DOCUMENT_MODE == 10; -}; - - -/** - * Checks whether the user agent is Edge. - * @param {string} userAgent The browser user agent string. - * @return {boolean} True if it is Edge. - */ -fireauth.util.isEdge = function(userAgent) { - return /Edge\/\d+/.test(userAgent); -}; - - -/** - * @param {?string=} opt_userAgent The navigator user agent. - * @return {boolean} Whether local storage is not synchronized between an iframe - * and a popup of the same domain. - */ -fireauth.util.isLocalStorageNotSynchronized = function(opt_userAgent) { - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - return fireauth.util.isIe11() || fireauth.util.isEdge(ua); -}; - - -/** @return {string} The current URL. */ -fireauth.util.getCurrentUrl = function() { - return (goog.global['window'] && goog.global['window']['location']['href']) || - // Check for worker environments. - (self && self['location'] && self['location']['href']) || ''; -}; - - -/** - * @param {string} requestUri The request URI to send in verifyAssertion - * request. - * @return {string} The sanitized URI, in this case it undoes the hashbang - * angularJs routing changes to request URI. - */ -fireauth.util.sanitizeRequestUri = function(requestUri) { - // If AngularJS is included. - if (typeof angular != 'undefined') { - // Remove hashbang modifications from URL. - requestUri = requestUri.replace('#/', '#').replace('#!/', '#'); - } - return requestUri; -}; - - -/** - * @param {?string} url The target URL. When !url, redirects to a blank page. - * @param {!Window=} opt_window The optional window to redirect to target URL. - * @param {boolean=} opt_bypassCheck Whether to bypass check. Used for custom - * scheme redirects. - */ -fireauth.util.goTo = function(url, opt_window, opt_bypassCheck) { - var win = opt_window || goog.global['window']; - // No URL, redirect to blank page. - var finalUrl = 'about:blank'; - // Popping up a window and then assigning its URL seems to cause some weird - // error. Fixed by setting win.location.href for now in IE browsers. - // Bug was detected in Edge and IE9. - if (url && !opt_bypassCheck) { - // We cannot use goog.dom.safe.setLocationHref since it tries to read - // popup.location from a different origin, which is an error in IE. - // (In Chrome, popup.location is just an empty Location object) - finalUrl = goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url)); - } - win.location.href = finalUrl; -}; - - -/** - * @param {string} url The target URL. - * @param {!Window=} opt_window The optional window to replace with target URL. - * @param {boolean=} opt_bypassCheck Whether to bypass check. Used for custom - * scheme redirects. - */ -fireauth.util.replaceCurrentUrl = function(url, opt_window, opt_bypassCheck) { - var win = opt_window || goog.global['window']; - if (!opt_bypassCheck) { - win.location.replace( - goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url))); - } else { - win.location.replace(url); - } -}; - - -/** - * Deep comparison of two objects. - * @param {!Object} a The first object. - * @param {!Object} b The second object. - * @return {!Array} The list of keys that are different between both - * objects provided. - */ -fireauth.util.getKeyDiff = function(a, b) { - var diff = []; - for (var k in a) { - if (!(k in b)) { - diff.push(k); - } else if (typeof a[k] != typeof b[k]) { - diff.push(k); - } else if (typeof a[k] == 'object' && a[k] != null && b[k] != null) { - if (fireauth.util.getKeyDiff( - a[k], b[k]).length > 0) { - diff.push(k); - } - } else if (a[k] !== b[k]) { - diff.push(k); - } - } - for (var k in b) { - if (!(k in a)) { - diff.push(k); - } - } - return diff; -}; - - -/** - * @param {?string=} opt_userAgent The navigator user agent. - * @return {?number} The Chrome version, null if the user agent is not Chrome. - */ -fireauth.util.getChromeVersion = function(opt_userAgent) { - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - var browserName = fireauth.util.getBrowserName(ua); - // Confirm current browser is Chrome. - if (browserName != fireauth.util.BrowserName.CHROME) { - return null; - } - var matches = ua.match(/\sChrome\/(\d+)/i); - if (matches && matches.length == 2) { - return parseInt(matches[1], 10); - } - return null; -}; - - -/** - * Detects CORS support. - * @param {?string=} opt_userAgent The navigator user agent. - * @return {boolean} True if the browser supports CORS. - */ -fireauth.util.supportsCors = function(opt_userAgent) { - // Chrome 7 has CORS issues, pick 30 as upper limit. - var chromeVersion = fireauth.util.getChromeVersion(opt_userAgent); - if (chromeVersion && chromeVersion < 30) { - return false; - } - // Among all other supported browsers, only IE8 and IE9 don't support CORS. - return !goog.userAgent.IE || // Not IE. - !goog.userAgent.DOCUMENT_MODE || // No document mode == IE Edge. - goog.userAgent.DOCUMENT_MODE > 9; -}; - - -/** - * Detects whether browser is running on a mobile device. - * @param {?string=} opt_userAgent The navigator user agent. - * @return {boolean} True if the browser is running on a mobile device. - */ -fireauth.util.isMobileBrowser = function(opt_userAgent) { - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - var uaLower = ua.toLowerCase(); - // TODO: implement getBrowserName equivalent for OS. - if (uaLower.match(/android/) || - uaLower.match(/webos/) || - uaLower.match(/iphone|ipad|ipod/) || - uaLower.match(/blackberry/) || - uaLower.match(/windows phone/) || - uaLower.match(/iemobile/)) { - return true; - } - return false; -}; - - -/** - * Closes the provided window. - * @param {?Window=} opt_window The optional window to close. The current window - * is used if none is provided. - */ -fireauth.util.closeWindow = function(opt_window) { - var win = opt_window || goog.global['window']; - // In some browsers, in certain cases after the window closes, as seen in - // Samsung Galaxy S3 Android 4.4.2 stock browser, the win object here is an - // empty object {}. Try to catch the failure and ignore it. - try { - win.close(); - } catch(e) {} -}; - - -/** - * Opens a popup window. - * @param {?string=} opt_url initial URL of the popup window - * @param {string=} opt_name title of the popup - * @param {?number=} opt_width width of the popup - * @param {?number=} opt_height height of the popup - * @return {?Window} Returns the window object that was opened. This returns - * null if a popup blocker prevented the window from being - * opened. - */ -fireauth.util.popup = function(opt_url, opt_name, opt_width, opt_height) { - var width = opt_width || 500; - var height = opt_height || 600; - var top = (window.screen.availHeight - height) / 2; - var left = (window.screen.availWidth - width) / 2; - var options = { - 'width': width, - 'height': height, - 'top': top > 0 ? top : 0, - 'left': left > 0 ? left : 0, - 'location': true, - 'resizable': true, - 'statusbar': true, - 'toolbar': false - }; - // Chrome iOS 7 and 8 is returning an undefined popup win when target is - // specified, even though the popup is not necessarily blocked. - var ua = fireauth.util.getUserAgentString().toLowerCase(); - if (opt_name) { - options['target'] = opt_name; - // This will force a new window on each call, achieving the same effect as - // passing a random name on each call. - if (goog.string.contains(ua, 'crios/')) { - options['target'] = '_blank'; - } - } - var browserName = fireauth.util.getBrowserName( - fireauth.util.getUserAgentString()); - if (browserName == fireauth.util.BrowserName.FIREFOX) { - // Firefox complains when invalid URLs are popped out. Hacky way to bypass. - opt_url = opt_url || 'http://localhost'; - // Firefox disables by default scrolling on popup windows, which can create - // issues when the user has many Google accounts, for instance. - options['scrollbars'] = true; - } - // about:blank getting sanitized causing browsers like IE/Edge to display - // brief error message before redirecting to handler. - var newWin = goog.window.open(opt_url || '', options); - if (newWin) { - // Flaky on IE edge, encapsulate with a try and catch. - try { - newWin.focus(); - } catch (e) {} - } - return newWin; -}; - - -/** - * The default value for the popup wait cycle in ms. - * @const {number} - * @private - */ -fireauth.util.POPUP_WAIT_CYCLE_MS_ = 2000; - - -/** - * @param {?string=} opt_userAgent The optional user agent. - * @return {boolean} Whether the popup requires a delay before closing itself. - */ -fireauth.util.requiresPopupDelay = function(opt_userAgent) { - // TODO: remove this hack when CriOS behavior is fixed in iOS. - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - // Was observed in iOS 10.2 Chrome version 55.0.2883.79. - // Apply to Chrome 55+ iOS 10+ to ensure future Chrome versions or iOS 10 - // minor updates do not suddenly resurface this bug. Revisit this check on - // next CriOS update. - var matches = ua.match(/OS (\d+)_.*CriOS\/(\d+)\./i); - if (matches && matches.length > 2) { - // iOS 10+ && chrome 55+. - return parseInt(matches[1], 10) >= 10 && parseInt(matches[2], 10) >= 55; - } - return false; -}; - - -/** - * @param {?Window} win The popup window to check. - * @param {number=} opt_stepDuration The duration of each wait cycle before - * checking that window is closed. - * @return {!goog.Promise} The promise to resolve when window is - * closed. - */ -fireauth.util.onPopupClose = function(win, opt_stepDuration) { - var stepDuration = opt_stepDuration || fireauth.util.POPUP_WAIT_CYCLE_MS_; - return new goog.Promise(function(resolve, reject) { - // Function to repeat each stepDuration. - var repeat = function() { - goog.Timer.promise(stepDuration).then(function() { - // After wait, check if window is closed. - if (!win || win.closed) { - // If so, resolve. - resolve(); - } else { - // Call repeat again. - return repeat(); - } - }); - }; - return repeat(); - }); -}; - - -/** - * @param {!Array} authorizedDomains List of authorized domains. - * @param {string} url The URL to check. - * @return {boolean} Whether the passed domain is an authorized one. - */ -fireauth.util.isAuthorizedDomain = function(authorizedDomains, url) { - var uri = goog.Uri.parse(url); - var scheme = uri.getScheme(); - var domain = uri.getDomain(); - for (var i = 0; i < authorizedDomains.length; i++) { - // Currently this corresponds to: domain.com = *://*.domain.com:* or - // exact domain match. - // In the case of Chrome extensions, the authorizedDomain will be formatted - // as 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456'. - // The URL to check must have a chrome extension scheme and the domain - // must be an exact match domain == 'abcdefghijklmnopqrstuvwxyz123456'. - if (fireauth.util.matchDomain(authorizedDomains[i], domain, scheme)) { - return true; - } - } - return false; -}; - - -/** - * Represents the dimensions of an entity (width and height). - * @typedef {{ - * width: number, - * height: number - * }} - */ -fireauth.util.Dimensions; - - -/** - * @param {?Window=} opt_window The optional window whose dimensions are to be - * returned. The current window is used if not found. - * @return {?fireauth.util.Dimensions} The requested window dimensions if - * available. - */ -fireauth.util.getWindowDimensions = function(opt_window) { - var win = opt_window || goog.global['window']; - if (win && win['innerWidth'] && win['innerHeight']) { - return { - 'width': parseFloat(win['innerWidth']), - 'height': parseFloat(win['innerHeight']) - }; - } - return null; -}; - - -/** - * RegExp to detect if the domain given is an IP address. This is only used - * for validating http and https schemes. - * - * It does not strictly validate if the IP is a real IP address, but as the - * matchDomain method tests against a set of valid domains (extracted from the - * window's current URL), it is sufficient. - * - * @const {!RegExp} - * @private - */ -fireauth.util.IP_ADDRESS_REGEXP_ = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; - - -/** - * @param {string} domainPattern The domain pattern to match. - * @param {string} domain The domain to check. It is assumed that it is a valid - * domain, not a user provided one. - * @param {string} scheme The scheme of the domain to check. - * @return {boolean} Whether the provided domain matches the domain pattern. - */ -fireauth.util.matchDomain = function(domainPattern, domain, scheme) { - // Chrome extension matching. - if (domainPattern.indexOf('chrome-extension://') == 0) { - var chromeExtUri = goog.Uri.parse(domainPattern); - // Domain must match and the current scheme must be a Chrome extension. - return chromeExtUri.getDomain() == domain && scheme == 'chrome-extension'; - } else if (scheme != 'http' && scheme != 'https') { - // Any other scheme that is not http or https cannot be whitelisted. - return false; - } else { - // domainPattern must not contain a scheme and the current scheme must be - // either http or https. - // Check if authorized domain pattern is an IP address. - if (fireauth.util.IP_ADDRESS_REGEXP_.test(domainPattern)) { - // The domain has to be exactly equal to the pattern, as an IP domain will - // only contain the IP, no extra character. - return domain == domainPattern; - } - // Dots in pattern should be escaped. - var escapedDomainPattern = domainPattern.split('.').join('\\.'); - // Non ip address domains. - // domain.com = *.domain.com OR domain.com - var re = new RegExp( - '^(.+\\.' + escapedDomainPattern + '|' + - escapedDomainPattern + ')$', 'i'); - return re.test(domain); - } -}; - - -/** - * RegExp to detect if the email address given is valid. - * @const {!RegExp} - * @private - */ -fireauth.util.EMAIL_ADDRESS_REGEXP_ = /^[^@]+@[^@]+$/; - - -/** - * Determines if it is a valid email address. - * @param {*} email The email address. - * @return {boolean} Whether the email address is valid. - */ -fireauth.util.isValidEmailAddress = function(email) { - return typeof email === 'string' && - fireauth.util.EMAIL_ADDRESS_REGEXP_.test(email); -}; - - -/** - * @return {!goog.Promise} A promise that resolves when DOM is ready. - */ -fireauth.util.onDomReady = function() { - var resolver = null; - return new goog.Promise(function(resolve, reject) { - var doc = goog.global.document; - // If document already loaded, resolve immediately. - if (doc.readyState == 'complete') { - resolve(); - } else { - // Document not ready, wait for load before resolving. - // Save resolver, so we can remove listener in case it was externally - // cancelled. - resolver = function() { - resolve(); - }; - goog.events.listenOnce(window, goog.events.EventType.LOAD, resolver); - } - }).thenCatch(function(error) { - // In case this promise was cancelled, make sure it unlistens to load. - goog.events.unlisten(window, goog.events.EventType.LOAD, resolver); - throw error; - }); -}; - - -/** @return {boolean} Whether environment supports DOM. */ -fireauth.util.isDOMSupported = function() { - return !!goog.global.document; -}; - - -/** - * The default ondeviceready Cordova timeout in ms. - * @const {number} - * @private - */ -fireauth.util.CORDOVA_ONDEVICEREADY_TIMEOUT_MS_ = 1000; - - -/** - * @param {?string=} opt_userAgent The optional user agent. - * @param {number=} opt_timeout The optional timeout in ms for deviceready - * event to resolve. - * @return {!goog.Promise} A promise that resolves if the current environment is - * a Cordova environment. - */ -fireauth.util.checkIfCordova = function(opt_userAgent, opt_timeout) { - // Errors generated are internal and should be converted if needed to - // developer facing Firebase errors. - // Only supported in Android/iOS environment. - if (fireauth.util.isAndroidOrIosCordovaScheme(opt_userAgent)) { - return fireauth.util.onDomReady().then(function() { - return new goog.Promise(function(resolve, reject) { - var doc = goog.global.document; - var timeoutId = setTimeout(function() { - reject(new Error('Cordova framework is not ready.')); - }, opt_timeout || fireauth.util.CORDOVA_ONDEVICEREADY_TIMEOUT_MS_); - // This should resolve immediately after DOM ready. - doc.addEventListener('deviceready', function() { - clearTimeout(timeoutId); - resolve(); - }, false); - }); - }); - } - return goog.Promise.reject( - new Error('Cordova must run in an Android or iOS file scheme.')); -}; - - -/** - * @param {?string=} opt_userAgent The optional user agent. - * @return {boolean} Whether the app is rendered in a mobile iOS or Android - * Cordova environment. - */ -fireauth.util.isAndroidOrIosCordovaScheme = function(opt_userAgent) { - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - return !!((fireauth.util.getCurrentScheme() === 'file:' || - fireauth.util.getCurrentScheme() === 'ionic:') && - ua.toLowerCase().match(/iphone|ipad|ipod|android/)); -}; - - -/** - * @param {?string=} opt_userAgent The optional user agent. - * @return {boolean} Whether the app is rendered in a mobile iOS 7 or 8 browser. - */ -fireauth.util.isIOS7Or8 = function(opt_userAgent) { - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - return !!(ua.match(/(iPad|iPhone|iPod).*OS 7_\d/i) || - ua.match(/(iPad|iPhone|iPod).*OS 8_\d/i)); -}; - - -/** - * @return {boolean} Whether browser is Safari or an iOS browser and page is - * embedded in an iframe. Local Storage does not synchronize with an iframe - * embedded on a page in a different domain but will still trigger storage - * event with storage changes. - */ -fireauth.util.isSafariLocalStorageNotSynced = function() { - var ua = fireauth.util.getUserAgentString(); - // Safari or iOS browser and embedded in an iframe. - if (!fireauth.util.iframeCanSyncWebStorage(ua) && fireauth.util.isIframe()) { - return true; - } - return false; -}; - - -/** - * @param {?Window=} opt_win Optional window to check whether it is an iframe. - * If not provided, the current window is checked. - * @return {boolean} Whether web page is running in an iframe. - */ -fireauth.util.isIframe = function(opt_win) { - var win = opt_win || goog.global['window']; - try { - // Check that the current window is not the top window. - // If so, return true. - return !!(win && win != win['top']); - } catch (e) { - return false; - } -}; - - -/** - * @param {?Window=} opt_win Optional window to check whether it has an opener - * that is an iframe. - * @return {boolean} Whether the web page was opened from an iframe. - */ -fireauth.util.isOpenerAnIframe = function(opt_win) { - var win = opt_win || goog.global['window']; - try { - // Get the opener if available. - var opener = win && win['opener']; - // Check if the opener is an iframe. If so, return true. - // Confirm opener is available, otherwise the current window is checked - // instead. - return !!(opener && fireauth.util.isIframe(opener)); - } catch (e) { - return false; - } -}; - - -/** - * @param {?Object=} global The optional global scope. - * @return {boolean} Whether current environment is a worker. - */ -fireauth.util.isWorker = function(global) { - var scope = global || goog.global; - // WorkerGlobalScope only defined in worker environment. - return typeof scope['WorkerGlobalScope'] !== 'undefined' && - typeof scope['importScripts'] === 'function'; -}; - - -/** - * @param {?Object=} opt_global The optional global scope. - * @return {boolean} Whether current environment supports fetch API and other - * APIs it depends on. - */ -fireauth.util.isFetchSupported = function(opt_global) { - // Required by fetch API calls. - var scope = opt_global || goog.global; - return typeof scope['fetch'] !== 'undefined' && - typeof scope['Headers'] !== 'undefined' && - typeof scope['Request'] !== 'undefined'; -}; - - -/** - * Enum for the runtime environment. - * @enum {string} - */ -fireauth.util.Env = { - BROWSER: 'Browser', - NODE: 'Node', - REACT_NATIVE: 'ReactNative', - WORKER: 'Worker' -}; - - -/** - * @return {!fireauth.util.Env} The current runtime environment. - */ -fireauth.util.getEnvironment = function() { - if (firebase.INTERNAL.hasOwnProperty('reactNative')) { - return fireauth.util.Env.REACT_NATIVE; - } else if (firebase.INTERNAL.hasOwnProperty('node')) { - // browserify seems to keep the process property in some cases even though - // the library is browser only. Use this check instead to reliably detect - // a Node.js environment. - return fireauth.util.Env.NODE; - } else if (fireauth.util.isWorker()) { - // Worker environment. - return fireauth.util.Env.WORKER; - } - // The default is a browser environment. - return fireauth.util.Env.BROWSER; -}; - - -/** - * @return {boolean} Whether the environment is a native environment, where - * CORS checks do not apply. - */ -fireauth.util.isNativeEnvironment = function() { - var environment = fireauth.util.getEnvironment(); - return environment === fireauth.util.Env.REACT_NATIVE || - environment === fireauth.util.Env.NODE; -}; - - -/** - * The separator for storage keys to concatenate App name and API key. - * @const {string} - * @private - */ -fireauth.util.STORAGE_KEY_SEPARATOR_ = ':'; - - -/** - * @param {string} apiKey The API Key of the app. - * @param {string} appName The App name. - * @return {string} The key used for identifying the app owner of the user. - */ -fireauth.util.createStorageKey = function(apiKey, appName) { - return apiKey + fireauth.util.STORAGE_KEY_SEPARATOR_ + appName; -}; - - -/** @return {string} a long random character string. */ -fireauth.util.generateRandomString = function() { - return Math.floor(Math.random() * 1000000000).toString(); -}; - - -/** - * Generates a random alpha numeric string. - * @param {number} numOfChars The number of random characters within the string. - * @return {string} A string with a specific number of random characters. - */ -fireauth.util.generateRandomAlphaNumericString = function(numOfChars) { - var chars = []; - var allowedChars = - '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - while (numOfChars > 0) { - chars.push( - allowedChars.charAt( - Math.floor(Math.random() * allowedChars.length))); - numOfChars--; - } - return chars.join(''); -}; - - -/** - * Enums for Browser name. - * @enum {string} - */ -fireauth.util.BrowserName = { - ANDROID: 'Android', - BLACKBERRY: 'Blackberry', - EDGE: 'Edge', - FIREFOX: 'Firefox', - IE: 'IE', - IEMOBILE: 'IEMobile', - OPERA: 'Opera', - OTHER: 'Other', - CHROME: 'Chrome', - SAFARI: 'Safari', - SILK: 'Silk', - WEBOS: 'Webos' -}; - - -/** - * @param {string} userAgent The navigator user agent string. - * @return {string} The browser name, eg Safari, Firefox, etc. - */ -fireauth.util.getBrowserName = function(userAgent) { - var ua = userAgent.toLowerCase(); - if (goog.string.contains(ua, 'opera/') || - goog.string.contains(ua, 'opr/') || - goog.string.contains(ua, 'opios/')) { - return fireauth.util.BrowserName.OPERA; - } else if (goog.string.contains(ua, 'iemobile')) { - // Windows phone IEMobile browser. - return fireauth.util.BrowserName.IEMOBILE; - } else if (goog.string.contains(ua, 'msie') || - goog.string.contains(ua, 'trident/')) { - return fireauth.util.BrowserName.IE; - } else if (goog.string.contains(ua, 'edge/')) { - return fireauth.util.BrowserName.EDGE; - } else if (goog.string.contains(ua, 'firefox/')) { - return fireauth.util.BrowserName.FIREFOX; - } else if (goog.string.contains(ua, 'silk/')) { - return fireauth.util.BrowserName.SILK; - } else if (goog.string.contains(ua, 'blackberry')) { - // Blackberry browser. - return fireauth.util.BrowserName.BLACKBERRY; - } else if (goog.string.contains(ua, 'webos')) { - // WebOS default browser. - return fireauth.util.BrowserName.WEBOS; - } else if (goog.string.contains(ua, 'safari/') && - !goog.string.contains(ua, 'chrome/') && - !goog.string.contains(ua, 'crios/') && - !goog.string.contains(ua, 'android')) { - return fireauth.util.BrowserName.SAFARI; - } else if ((goog.string.contains(ua, 'chrome/') || - goog.string.contains(ua, 'crios/')) && - !goog.string.contains(ua, 'edge/')) { - return fireauth.util.BrowserName.CHROME; - } else if (goog.string.contains(ua, 'android')) { - // Android stock browser. - return fireauth.util.BrowserName.ANDROID; - } else { - // Most modern browsers have name/version at end of user agent string. - var re = new RegExp('([a-zA-Z\\d\\.]+)\/[a-zA-Z\\d\\.]*$'); - var matches = userAgent.match(re); - if (matches && matches.length == 2) { - return matches[1]; - } - } - return fireauth.util.BrowserName.OTHER; -}; - - -/** - * Enums for client implementation name. - * @enum {string} - */ -fireauth.util.ClientImplementation = { - JSCORE: 'JsCore', - OAUTH_HANDLER: 'Handler', - OAUTH_IFRAME: 'Iframe' -}; - - -/** - * Enums for the framework ID to be logged in RPC header. - * Future frameworks to possibly add: angularfire, polymerfire, reactfire, etc. - * @enum {string}. - */ -fireauth.util.Framework = { - // No other framework used. - DEFAULT: 'FirebaseCore-web', - // Firebase Auth used with FirebaseUI-web. - FIREBASEUI: 'FirebaseUI-web' -}; - - -/** - * @param {!Array} providedFrameworks List of framework ID strings. - * @return {!Array} List of supported framework IDs - * with no duplicates. - */ -fireauth.util.getFrameworkIds = function(providedFrameworks) { - var frameworkVersion = []; - var frameworkSet = {}; - for (var key in fireauth.util.Framework) { - frameworkSet[fireauth.util.Framework[key]] = true; - } - for (var i = 0; i < providedFrameworks.length; i++) { - if (typeof frameworkSet[providedFrameworks[i]] !== 'undefined') { - // Delete it from set to prevent duplications. - delete frameworkSet[providedFrameworks[i]]; - frameworkVersion.push(providedFrameworks[i]); - } - } - // Sort alphabetically so that "FirebaseCore-web,FirebaseUI-web" and - // "FirebaseUI-web,FirebaseCore-web" aren't viewed as different. - frameworkVersion.sort(); - return frameworkVersion; -}; - - -/** - * @param {!fireauth.util.ClientImplementation} clientImplementation The client - * implementation. - * @param {string} clientVersion The client version. - * @param {?Array=} opt_frameworkVersion The framework version. - * @param {?string=} opt_userAgent The optional user agent. - * @return {string} The full client SDK version. - */ -fireauth.util.getClientVersion = function(clientImplementation, clientVersion, - opt_frameworkVersion, opt_userAgent) { - var frameworkVersion = fireauth.util.getFrameworkIds( - opt_frameworkVersion || []); - if (!frameworkVersion.length) { - frameworkVersion = [fireauth.util.Framework.DEFAULT]; - } - var environment = fireauth.util.getEnvironment(); - var reportedEnvironment = ''; - if (environment === fireauth.util.Env.BROWSER) { - // In a browser environment, report the browser name. - var userAgent = opt_userAgent || fireauth.util.getUserAgentString(); - reportedEnvironment = fireauth.util.getBrowserName(userAgent); - } else if (environment === fireauth.util.Env.WORKER) { - // Technically a worker runs from a browser but we need to differentiate a - // worker from a browser. - // For example: Chrome-Worker/JsCore/4.9.1/FirebaseCore-web. - var userAgent = opt_userAgent || fireauth.util.getUserAgentString(); - reportedEnvironment = fireauth.util.getBrowserName(userAgent) + '-' + - environment; - } else { - // Otherwise, just report the environment name. - reportedEnvironment = environment; - } - // The format to be followed: - // ${browserName}/${clientImplementation}/${clientVersion}/${frameworkVersion} - // As multiple Firebase frameworks/libraries can be used, join their IDs with - // a comma. - return reportedEnvironment + '/' + clientImplementation + - '/' + clientVersion + '/' + frameworkVersion.join(','); -}; - - -/** - * @return {string} The user agent string reported by the environment, or the - * empty string if not available. - */ -fireauth.util.getUserAgentString = function() { - return (goog.global['navigator'] && goog.global['navigator']['userAgent']) || - ''; -}; - - -/** - * @param {string} varStrName The variable string name. - * @param {?Object=} opt_scope The optional scope where to look in. The default - * is window. - * @return {*} The reference if found. - */ -fireauth.util.getObjectRef = function(varStrName, opt_scope) { - var pieces = varStrName.split('.'); - var last = opt_scope || goog.global; - for (var i = 0; - i < pieces.length && typeof last == 'object' && last != null; - i++) { - last = last[pieces[i]]; - } - // Last hasn't reached the end yet, return undefined. - if (i != pieces.length) { - last = undefined; - } - return last; -}; - - -/** @return {boolean} Whether web storage is supported. */ -fireauth.util.isWebStorageSupported = function() { - try { - var storage = goog.global['localStorage']; - var key = fireauth.util.generateEventId(); - if (storage) { - // setItem will throw an exception if we cannot access WebStorage (e.g., - // Safari in private mode). - storage['setItem'](key, '1'); - storage['removeItem'](key); - // For browsers where iframe web storage does not synchronize with a popup - // of the same domain, indexedDB is used for persistent storage. These - // browsers include IE11 and Edge. - // Make sure it is supported (IE11 and Edge private mode does not support - // that). - if (fireauth.util.isLocalStorageNotSynchronized()) { - // In such browsers, if indexedDB is not supported, an iframe cannot be - // notified of the popup sign in result. - return !!goog.global['indexedDB']; - } - return true; - } - } catch (e) { - // localStorage is not available from a worker. Test availability of - // indexedDB. - return fireauth.util.isWorker() && !!goog.global['indexedDB']; - } - return false; -}; - - -/** - * This guards against leaking Cordova support before official launch. - * This field will be removed or updated to return true when the new feature is - * ready for launch. - * @return {boolean} Whether Cordova OAuth support is enabled. - */ -fireauth.util.isCordovaOAuthEnabled = function() { - return false; -}; - - -/** - * @return {boolean} Whether popup and redirect operations are supported in the - * current environment. - */ -fireauth.util.isPopupRedirectSupported = function() { - // Popup and redirect are supported in an environment where the container - // origin can be securely whitelisted. - return (fireauth.util.isHttpOrHttps() || - fireauth.util.isChromeExtension() || - fireauth.util.isAndroidOrIosCordovaScheme()) && - // React Native with remote debugging reports its location.protocol as - // http. - !fireauth.util.isNativeEnvironment() && - // Local storage has to be supported for browser popup and redirect - // operations to work. - fireauth.util.isWebStorageSupported() && - // DOM, popups and redirects are not supported within a worker. - !fireauth.util.isWorker(); -}; - - -/** - * @return {boolean} Whether the current environment is http or https. - */ -fireauth.util.isHttpOrHttps = function() { - return fireauth.util.getCurrentScheme() === 'http:' || - fireauth.util.getCurrentScheme() === 'https:'; -}; - - -/** @return {?string} The current URL scheme. */ -fireauth.util.getCurrentScheme = function() { - return (goog.global['location'] && goog.global['location']['protocol']) || - null; -}; - - -/** - * Checks whether the current page is a Chrome extension. - * @return {boolean} Whether the current page is a Chrome extension. - */ -fireauth.util.isChromeExtension = function() { - return fireauth.util.getCurrentScheme() === 'chrome-extension:'; -}; - - -/** - * @param {?string=} opt_userAgent The optional user agent. - * @return {boolean} Whether the current browser is running in an iOS - * environment. - */ -fireauth.util.isIOS = function(opt_userAgent) { - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - return !!ua.toLowerCase().match(/iphone|ipad|ipod/); -}; - - -/** - * @param {?string=} opt_userAgent The optional user agent. - * @return {boolean} Whether the current browser is running in an Android - * environment. - */ -fireauth.util.isAndroid = function(opt_userAgent) { - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - return !!ua.toLowerCase().match(/android/); -}; - - -/** - * @param {?string=} opt_userAgent The optional user agent. - * @return {boolean} Whether the opener of a popup cannot communicate with the - * popup while it is in the foreground. - */ -fireauth.util.runsInBackground = function(opt_userAgent) { - // TODO: split this check into 2, one check that opener can access - // popup, another check that storage synchronizes between popup and opener. - // Popup events fail in iOS version 7 (lowest version we currently support) - // browsers. When the popup is triggered, the opener is unable to redirect - // the popup url, close the popup and in some cases will miss the storage - // event triggered when localStorage is changed. - // Extend this to all mobile devices. This behavior is more likely to work - // cross mobile platforms. - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - if (fireauth.util.isMobileBrowser(ua)) { - return false; - } else if (fireauth.util.getBrowserName(ua) == - fireauth.util.BrowserName.FIREFOX) { - // Latest version of Firefox 47.0 does not allow you to access properties on - // the popup window from the opener. - return false; - } - return true; -}; - - -/** - * Stringifies an object, retuning null if the object is not defined. - * @param {*} obj The raw object. - * @return {?string} The JSON-serialized object. - */ -fireauth.util.stringifyJSON = function(obj) { - if (typeof obj === 'undefined') { - return null; - } - return goog.json.serialize(obj); -}; - - -/** - * @param {!Object} obj The original object. - * @return {!Object} A copy of the original object with all entries that are - * null or undefined removed. - */ -fireauth.util.copyWithoutNullsOrUndefined = function(obj) { - // The processed copy to return. - var trimmedObj = {}; - // Remove all empty fields from data, allow zero and false booleans. - for (var key in obj) { - if (obj.hasOwnProperty(key) && - obj[key] !== null && - obj[key] !== undefined) { - trimmedObj[key] = obj[key]; - } - } - return trimmedObj; -}; - - -/** - * Removes all key/pairs with the specified keys from the given object. - * @param {!Object} obj The object to process. - * @param {!Array} keys The list of keys to remove. - * @return {!Object} The object with the keys removed. - */ -fireauth.util.removeEntriesWithKeys = function(obj, keys) { - // Clone object. - var copy = goog.object.clone(obj); - // Traverse keys. - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - // If key found in object, remove it. - if (key in copy) { - delete copy[key]; - } - } - // Returned filtered copy. - return copy; -}; - - -/** - * Parses a JSON string, returning undefined if null is passed. - * @param {?string} json The JSON-serialized object. - * @return {*} The raw object. - */ -fireauth.util.parseJSON = function(json) { - if (json === null) { - return undefined; - } - - // Do not use goog.json.parse since it uses eval underneath to support old - // browsers that do not provide JSON.parse. The recommended Content Security - // Policy does not allow unsafe-eval in some environments like Chrome - // extensions. Usage of eval is not recommend in Chrome in general. - // Use native parsing instead via JSON.parse. This is provided in our list - // of supported browsers. - return JSON.parse(json); -}; - - -/** - * @param {?string=} opt_prefix An optional prefix string to prepend to ID. - * @return {string} The generated event ID used to identify a generic event. - */ -fireauth.util.generateEventId = function(opt_prefix) { - return opt_prefix ? opt_prefix : '' + - Math.floor(Math.random() * 1000000000).toString(); -}; - - -/** - * @param {?string=} opt_userAgent The optional user agent. - * @return {boolean} Whether an embedded iframe can sync to web storage changes. - * Web storage sync fails in Safari desktop browsers and iOS mobile - * browsers. - */ -fireauth.util.iframeCanSyncWebStorage = function(opt_userAgent) { - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - if (fireauth.util.getBrowserName(ua) == fireauth.util.BrowserName.SAFARI || - ua.toLowerCase().match(/iphone|ipad|ipod/)) { - return false; - } - return true; -}; - - -/** - * Reset unlaoded GApi modules. If gapi.load fails due to a network error, - * it will stop working after a retrial. This is a hack to fix this issue. - */ -fireauth.util.resetUnloadedGapiModules = function() { - // Clear last failed gapi.load state to force next gapi.load to first - // load the failed gapi.iframes module. - // Get gapix.beacon context. - var beacon = goog.global['___jsl']; - // Get current hint. - if (beacon && beacon['H']) { - // Get gapi hint. - for (var hint in beacon['H']) { - // Requested modules. - beacon['H'][hint]['r'] = beacon['H'][hint]['r'] || []; - // Loaded modules. - beacon['H'][hint]['L'] = beacon['H'][hint]['L'] || []; - // Set requested modules to a copy of the loaded modules. - beacon['H'][hint]['r'] = beacon['H'][hint]['L'].concat(); - // Clear pending callbacks. - if (beacon['CP']) { - for (var i = 0; i < beacon['CP'].length; i++) { - // Remove all failed pending callbacks. - beacon['CP'][i] = null; - } - } - } - } -}; - - -/** - * Returns whether the current device is a mobile device. Mobile browsers and - * React-Native environments are considered mobile devices. - * @param {?string=} opt_userAgent The optional navigator user agent. - * @param {?fireauth.util.Env=} opt_env The optional environment. - * @return {boolean} Whether the current device is a mobile device or not. - */ -fireauth.util.isMobileDevice = function(opt_userAgent, opt_env) { - // Get user agent. - var ua = opt_userAgent || fireauth.util.getUserAgentString(); - // Get environment. - var environment = opt_env || fireauth.util.getEnvironment(); - return fireauth.util.isMobileBrowser(ua) || - environment === fireauth.util.Env.REACT_NATIVE; -}; - - -/** - * @param {?Object=} opt_navigator The optional navigator object typically used - * for testing. - * @return {boolean} Whether the app is currently online. If offline, false is - * returned. If this cannot be determined, true is returned. - */ -fireauth.util.isOnline = function(opt_navigator) { - var navigator = opt_navigator || goog.global['navigator']; - if (navigator && - typeof navigator['onLine'] === 'boolean' && - // Apply only for traditional web apps and Chrome extensions. - // This is especially true for Cordova apps which have unreliable - // navigator.onLine behavior unless cordova-plugin-network-information is - // installed which overwrites the native navigator.onLine value and - // defines navigator.connection. - (fireauth.util.isHttpOrHttps() || - fireauth.util.isChromeExtension() || - typeof navigator['connection'] !== 'undefined')) { - return navigator['onLine']; - } - // If we can't determine the state, assume it is online. - return true; -}; - - -/** - * @param {?Object=} opt_navigator The object with navigator data, defaulting - * to window.navigator if unspecified. - * @return {?string} The user's preferred language. Returns null if - */ -fireauth.util.getUserLanguage = function(opt_navigator) { - var navigator = opt_navigator || goog.global['navigator']; - if (!navigator) { - return null; - } - return ( - // Most reliable, but only supported in Chrome/Firefox. - navigator['languages'] && navigator['languages'][0] || - // Supported in most browsers, but returns the language of the browser - // UI, not the language set in browser settings. - navigator['language'] || - // IE <= 10. - navigator['userLanguage'] || - // Couldn't determine language. - null - ); -}; - - -/** - * A structure to help pick between a range of long and short delay durations - * depending on the current environment. In general, the long delay is used for - * mobile environments whereas short delays are used for desktop environments. - * @param {number} shortDelay The short delay duration. - * @param {number} longDelay The long delay duration. - * @param {?string=} opt_userAgent The optional navigator user agent. - * @param {?fireauth.util.Env=} opt_env The optional environment. - * @constructor - */ -fireauth.util.Delay = function(shortDelay, longDelay, opt_userAgent, opt_env) { - // Internal error when improperly initialized. - if (shortDelay > longDelay) { - throw new Error('Short delay should be less than long delay!'); - } - /** - * @private @const {number} The short duration delay used for desktop - * environments. - */ - this.shortDelay_ = shortDelay; - /** - * @private @const {number} The long duration delay used for mobile - * environments. - */ - this.longDelay_ = longDelay; - /** @private @const {boolean} Whether the environment is a mobile one. */ - this.isMobile_ = fireauth.util.isMobileDevice(opt_userAgent, opt_env); -}; - - -/** - * The default value for the offline delay timeout in ms. - * @const {number} - * @private - */ -fireauth.util.Delay.OFFLINE_DELAY_MS_ = 5000; - - -/** - * @return {number} The delay that matches with the current environment. - */ -fireauth.util.Delay.prototype.get = function() { - // navigator.onLine is unreliable in some cases. - // Failing hard in those cases may make it impossible to recover for end user. - // Waiting for the regular full duration when there is no network can result - // in a bad experience. - // Instead return a short timeout duration. If there is no network connection, - // the user would wait 5 seconds to detect that. If there is a connection - // (false alert case), the user still has the ability to try to send the - // request. If it fails (timeout too short), they can still retry. - if (!fireauth.util.isOnline()) { - // Pick the shorter timeout. - return Math.min(fireauth.util.Delay.OFFLINE_DELAY_MS_, this.shortDelay_); - } - // If running in a mobile environment, return the long delay, otherwise - // return the short delay. - // This could be improved in the future to dynamically change based on other - // variables instead of just reading the current environment. - return this.isMobile_ ? this.longDelay_ : this.shortDelay_; -}; - - -/** - * @return {boolean} Whether the app is visible in the foreground. This uses - * document.visibilityState. For browsers that do not support it, this is - * always true. - */ -fireauth.util.isAppVisible = function() { - // https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState - var doc = goog.global.document; - // Check if supported. - if (doc && typeof doc['visibilityState'] !== 'undefined') { - // Check if visible. - return doc['visibilityState'] == 'visible'; - } - // API not supported in current browser, default to true. - return true; -}; - - -/** - * @return {!goog.Promise} A promise that resolves when the app is visible in - * the foreground. - */ -fireauth.util.onAppVisible = function() { - var doc = goog.global.document; - // Visibility change listener reference. - var onVisibilityChange = null; - if (fireauth.util.isAppVisible() || !doc) { - // Visible or non browser environment. - return goog.Promise.resolve(); - } else { - // Invisible and in browser environment. - return new goog.Promise(function(resolve, reject) { - // On visibility change listener. - onVisibilityChange = function(event) { - // App is visible. - if (fireauth.util.isAppVisible()) { - // Unregister event listener. - doc.removeEventListener( - 'visibilitychange', onVisibilityChange, false); - // Resolve promise. - resolve(); - } - }; - // Listen to visibility change. - doc.addEventListener('visibilitychange', onVisibilityChange, false); - }).thenCatch(function(error) { - // In case this promise was cancelled, make sure it unlistens to - // visibilitychange event. - doc.removeEventListener('visibilitychange', onVisibilityChange, false); - // Rethrow the same error. - throw error; - }); - } -}; - - -/** - * Logs a warning message to the console, if the console is available. - * @param {string} message - */ -fireauth.util.consoleWarn = function(message) { - if (typeof console !== 'undefined' && typeof console.warn === 'function') { - console.warn(message); - } -}; - - -/** - * Logs an info message to the console, if the console is available. - * @param {string} message - */ -fireauth.util.consoleInfo = function(message) { - if (typeof console !== 'undefined' && typeof console.info === 'function') { - console.info(message); - } -}; - - -/** - * Parses a UTC time stamp string or number and returns the corresponding UTC - * date string if valid. Otherwise, returns null. - * @param {?string|number} utcTimestamp The UTC timestamp number or string. - * @return {?string} The corresponding UTC date string. Null if invalid. - */ -fireauth.util.utcTimestampToDateString = function(utcTimestamp) { - try { - // Convert to date object. - var date = new Date(parseInt(utcTimestamp, 10)); - // Test date is valid. - if (!isNaN(date.getTime()) && - // Confirm that utcTimestamp is numeric. - goog.string.isNumeric(utcTimestamp)) { - // Convert to UTC date string. - return date.toUTCString(); - } - } catch (e) { - // Do nothing. null will be returned. - } - return null; -}; - - -/** @return {boolean} Whether indexedDB is available. */ -fireauth.util.isIndexedDBAvailable = function() { - return !!goog.global['indexedDB']; -}; - - -/** @return {boolean} Whether current mode is Auth handler or iframe. */ -fireauth.util.isAuthHandlerOrIframe = function() { - return !!(fireauth.util.getObjectRef('fireauth.oauthhelper', goog.global) || - fireauth.util.getObjectRef('fireauth.iframe', goog.global)); -}; - - -/** @return {boolean} Whether indexedDB is used to persist storage. */ -fireauth.util.persistsStorageWithIndexedDB = function() { - // This will cover: - // IE11, Edge when indexedDB is available (this is unavailable in InPrivate - // mode). (SDK, OAuth handler and iframe) - // Any environment where indexedDB is available (SDK only). - - // In a browser environment, when an iframe and a popup web storage are not - // synchronized, use the indexedDB fireauth.storage.Storage implementation. - return (fireauth.util.isLocalStorageNotSynchronized() || - !fireauth.util.isAuthHandlerOrIframe()) && - fireauth.util.isIndexedDBAvailable(); -}; - - -/** Sets the no-referrer meta tag in the document head if applicable. */ -fireauth.util.setNoReferrer = function() { - var doc = goog.global.document; - if (doc) { - try { - var meta = goog.dom.createDom(goog.dom.TagName.META, { - 'name': 'referrer', - 'content': 'no-referrer' - }); - var headCollection = goog.dom.getElementsByTagName(goog.dom.TagName.HEAD); - // Append meta tag to head. - if (headCollection.length) { - headCollection[0].appendChild(meta); - } - } catch (e) { - // Best effort approach. - } - } -}; - - -/** @return {?ServiceWorker} The servicerWorker controller if available. */ -fireauth.util.getServiceWorkerController = function() { - var navigator = goog.global['navigator']; - return (navigator && - navigator.serviceWorker && - navigator.serviceWorker.controller) || null; -}; - - -/** @return {?WorkerGlobalScope} The worker global scope if available. */ -fireauth.util.getWorkerGlobalScope = function() { - return fireauth.util.isWorker() ? /** @type {!WorkerGlobalScope} */ (self) : - null; -}; - -/** - * @return {!goog.Promise} A promise that resolves with the - * service worker. This will resolve only when a service worker becomes - * available. If no service worker is supported, it will resolve with null. - */ -fireauth.util.getActiveServiceWorker = function() { - var navigator = goog.global['navigator']; - if (navigator && navigator.serviceWorker) { - return goog.Promise.resolve() - .then(function() { - return navigator.serviceWorker.ready; - }) - .then(function(registration) { - return /** @type {?ServiceWorker} */ (registration.active || null); - }) - .thenCatch(function(error) { - return null; - }); - } - return goog.Promise.resolve(/** @type {?ServiceWorker} */ (null)); -}; diff --git a/packages/auth/test/actioncodeinfo_test.js b/packages/auth/test/actioncodeinfo_test.js deleted file mode 100644 index cc9cbeec79e..00000000000 --- a/packages/auth/test/actioncodeinfo_test.js +++ /dev/null @@ -1,317 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for actioncodeinfo.js. - */ - -goog.provide('fireauth.ActionCodeInfoTest'); - -goog.require('fireauth.ActionCodeInfo'); -goog.require('fireauth.PhoneMultiFactorInfo'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.ActionCodeInfoTest'); - - -var now = new Date(); -var verifyEmailServerResponse = { - 'kind': 'identitytoolkit#ResetPasswordResponse', - 'email': 'user@example.com', - 'requestType': 'VERIFY_EMAIL' -}; -var passwordResetServerResponse = { - 'kind': 'identitytoolkit#ResetPasswordResponse', - 'email': 'user@example.com', - 'requestType': 'PASSWORD_RESET' -}; -var recoverEmailServerResponse = { - 'kind': 'identitytoolkit#ResetPasswordResponse', - 'email': 'user@example.com', - 'newEmail': 'newUser@example.com', - 'requestType': 'RECOVER_EMAIL' -}; -var signInWithEmailLinkServerResponse = { - 'kind': 'identitytoolkit#ResetPasswordResponse', - 'requestType': 'EMAIL_SIGNIN' -}; -var verifyAndChangeEmailResponse = { - 'kind': 'identitytoolkit#ResetPasswordResponse', - 'email': 'old@example.com', - 'newEmail': 'user@example.com', - 'requestType': 'VERIFY_AND_CHANGE_EMAIL' -}; -var revertSecondFactorAdditionResponse = { - 'kind': 'identitytoolkit#ResetPasswordResponse', - 'email': 'user@example.com', - 'mfaInfo': { - 'phoneInfo': '+*******1234', - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'displayName': 'Work phone', - 'enrolledAt': now.toISOString() - }, - 'requestType': 'REVERT_SECOND_FACTOR_ADDITION' -}; - - - -function testActionCodeInfo_invalid_missingOperation() { - assertThrows(function() { - new fireauth.ActionCodeInfo({'email': 'user@example.com'}); - }); -} - - -function testActionCodeInfo_invalid_missingEmail() { - assertThrows(function() { - new fireauth.ActionCodeInfo({'requestType': 'PASSWORD_RESET'}); - }); -} - - -function testActionCodeInfo_invalid_missingNewEmail() { - assertThrows(function() { - new fireauth.ActionCodeInfo({ - 'requestType': 'VERIFY_AND_CHANGE_EMAIL', - 'email': 'user@example.com' - }); - }); -} - - -function testActionCodeInfo_invalid_missingMfaInfo() { - assertThrows(function() { - new fireauth.ActionCodeInfo({ - 'requestType': 'REVERT_SECOND_FACTOR_ADDITION', - 'email': 'user@example.com' - }); - }); -} - - - -function testActionCodeInfo_verifyEmail() { - var expectedData = { - email: 'user@example.com', - fromEmail: null, - previousEmail: null, - multiFactorInfo: null - }; - var actionCodeInfo = new fireauth.ActionCodeInfo(verifyEmailServerResponse); - - // Check operation. - assertEquals('VERIFY_EMAIL', actionCodeInfo['operation']); - // Property should be read-only. - actionCodeInfo['operation'] = 'BLA'; - assertEquals('VERIFY_EMAIL', actionCodeInfo['operation']); - - // Check data. - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - // Property should be read-only. - actionCodeInfo['data']['email'] = 'other@example.com'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - actionCodeInfo['data'] = 'BLA'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); -} - - -function testActionCodeInfo_verifyAndChangeEmail() { - var expectedData = { - email: 'user@example.com', - fromEmail: 'old@example.com', - previousEmail: 'old@example.com', - multiFactorInfo: null - }; - var actionCodeInfo = - new fireauth.ActionCodeInfo(verifyAndChangeEmailResponse); - - // Check operation. - assertEquals('VERIFY_AND_CHANGE_EMAIL', actionCodeInfo['operation']); - // Property should be read-only. - actionCodeInfo['operation'] = 'BLA'; - assertEquals('VERIFY_AND_CHANGE_EMAIL', actionCodeInfo['operation']); - - // Check data. - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - // Property should be read-only. - actionCodeInfo['data']['email'] = 'other@example.com'; - actionCodeInfo['data']['fromEmail'] = 'unknown@example.com'; - actionCodeInfo['data']['previousEmail'] = 'unknown@example.com'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - actionCodeInfo['data'] = 'BLA'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); -} - - -function testActionCodeInfo_verifyAndChangeEmail_noPreviousEmail() { - var verifyAndChangeEmailNoEmailResponse = { - 'kind': 'identitytoolkit#ResetPasswordResponse', - 'email': '', - 'newEmail': 'user@example.com', - 'requestType': 'VERIFY_AND_CHANGE_EMAIL' - }; - var expectedData = { - email: 'user@example.com', - fromEmail: null, - previousEmail: null, - multiFactorInfo: null - }; - var actionCodeInfo = - new fireauth.ActionCodeInfo(verifyAndChangeEmailNoEmailResponse); - - // Check operation. - assertEquals('VERIFY_AND_CHANGE_EMAIL', actionCodeInfo['operation']); - // Check data. - assertObjectEquals( - expectedData, - actionCodeInfo['data']); -} - -function testActionCodeInfo_revertSecondFactorAddition() { - var info = new fireauth.PhoneMultiFactorInfo( - revertSecondFactorAdditionResponse['mfaInfo']); - var expectedData = { - email: 'user@example.com', - fromEmail: null, - previousEmail: null, - multiFactorInfo: info - }; - var actionCodeInfo = - new fireauth.ActionCodeInfo(revertSecondFactorAdditionResponse); - - // Check operation. - assertEquals('REVERT_SECOND_FACTOR_ADDITION', actionCodeInfo['operation']); - // Property should be read-only. - actionCodeInfo['operation'] = 'BLA'; - assertEquals('REVERT_SECOND_FACTOR_ADDITION', actionCodeInfo['operation']); - - // Check data. - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - // Property should be read-only. - actionCodeInfo['data']['email'] = 'other@example.com'; - actionCodeInfo['data']['multiFactorInfo'] = {}; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - actionCodeInfo['data'] = 'BLA'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); -} - - -function testActionCodeInfo_passwordReset() { - var expectedData = { - email: 'user@example.com', - fromEmail: null, - previousEmail: null, - multiFactorInfo: null - }; - var actionCodeInfo = new fireauth.ActionCodeInfo(passwordResetServerResponse); - - // Check operation. - assertEquals('PASSWORD_RESET', actionCodeInfo['operation']); - // Property should be read-only. - actionCodeInfo['operation'] = 'BLA'; - assertEquals('PASSWORD_RESET', actionCodeInfo['operation']); - - // Check data. - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - // Property should be read-only. - actionCodeInfo['data']['email'] = 'other@example.com'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - actionCodeInfo['data'] = 'BLA'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); -} - - -function testActionCodeInfo_recoverEmail() { - var expectedData = { - email: 'user@example.com', - fromEmail: 'newUser@example.com', - previousEmail: 'newUser@example.com', - multiFactorInfo: null - }; - var actionCodeInfo = new fireauth.ActionCodeInfo(recoverEmailServerResponse); - - // Check operation. - assertEquals('RECOVER_EMAIL', actionCodeInfo['operation']); - // Property should be read-only. - actionCodeInfo['operation'] = 'BLA'; - assertEquals('RECOVER_EMAIL', actionCodeInfo['operation']); - - // Check data. - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - // Property should be read-only. - actionCodeInfo['data']['email'] = 'other@example.com'; - actionCodeInfo['data']['fromEmail'] = 'unknown@example.com'; - actionCodeInfo['data']['previousEmail'] = 'unknown@example.com'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); - actionCodeInfo['data'] = 'BLA'; - assertObjectEquals( - expectedData, - actionCodeInfo['data']); -} - -function testActionCodeInfo_signInWithEmailLink() { - var expectedData = { - email: null, - fromEmail: null, - previousEmail: null, - multiFactorInfo: null - }; - var actionCodeInfo = - new fireauth.ActionCodeInfo(signInWithEmailLinkServerResponse); - - // Check operation. - assertEquals('EMAIL_SIGNIN', actionCodeInfo['operation']); - // Property should be read-only. - actionCodeInfo['operation'] = 'BLA'; - assertEquals('EMAIL_SIGNIN', actionCodeInfo['operation']); - - // Check data. - assertObjectEquals(expectedData, actionCodeInfo['data']); - // Property should be read-only. - actionCodeInfo['data']['email'] = 'other@example.com'; - assertObjectEquals(expectedData, actionCodeInfo['data']); - actionCodeInfo['data'] = 'BLA'; - assertObjectEquals(expectedData, actionCodeInfo['data']); -} diff --git a/packages/auth/test/actioncodesettings_test.js b/packages/auth/test/actioncodesettings_test.js deleted file mode 100644 index 6292672ebaa..00000000000 --- a/packages/auth/test/actioncodesettings_test.js +++ /dev/null @@ -1,346 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for actioncodesettings.js. - */ - -goog.provide('fireauth.ActionCodeSettingsTest'); - -goog.require('fireauth.ActionCodeSettings'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.ActionCodeSettingsTest'); - - -/** - * Asserts that the provided settings will throw an error in action code - * settings initialization. - * @param {!Object} settings The settings object to test for expected errors. - * @param {string} expectedCode The expected error code thrown. - */ -function assertActionCodeSettingsErrorThrown(settings, expectedCode) { - var error = assertThrows(function() { - new fireauth.ActionCodeSettings(settings); - }); - assertEquals(expectedCode, error.code); -} - - -function testActionCodeSettings_success_allParameters() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - 'iOS': { - 'bundleId': 'com.example.ios' - }, - 'android': { - 'packageName': 'com.example.android', - 'installApp': true, - 'minimumVersion': '12' - }, - 'handleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }; - var expectedRequest = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }; - var actionCodeSettings = new fireauth.ActionCodeSettings(settings); - assertObjectEquals(expectedRequest, actionCodeSettings.buildRequest()); -} - - -function testActionCodeSettings_success_partialParameters() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - // Will be ignored. - 'iOS': {}, - 'android': {} - }; - var expectedRequest = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'canHandleCodeInApp': false - }; - var actionCodeSettings = new fireauth.ActionCodeSettings(settings); - assertObjectEquals(expectedRequest, actionCodeSettings.buildRequest()); -} - - -function testActionCodeSettings_success_partialParameters_fdlDomain() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - // Will be ignored. - 'iOS': {}, - 'android': {}, - 'dynamicLinkDomain': 'example.page.link' - }; - var expectedRequest = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'canHandleCodeInApp': false, - 'dynamicLinkDomain': 'example.page.link' - }; - var actionCodeSettings = new fireauth.ActionCodeSettings(settings); - assertObjectEquals(expectedRequest, actionCodeSettings.buildRequest()); -} - - -function testActionCodeSettings_success_partialParameters_android() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - 'android': { - 'packageName': 'com.example.android' - } - }; - var expectedRequest = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': false, - 'canHandleCodeInApp': false - }; - var actionCodeSettings = new fireauth.ActionCodeSettings(settings); - assertObjectEquals(expectedRequest, actionCodeSettings.buildRequest()); -} - - -function testActionCodeSettings_success_partialParameters_android_fdlDomain() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - 'android': { - 'packageName': 'com.example.android' - }, - 'dynamicLinkDomain': 'example.page.link' - }; - var expectedRequest = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': false, - 'canHandleCodeInApp': false, - 'dynamicLinkDomain': 'example.page.link' - }; - var actionCodeSettings = new fireauth.ActionCodeSettings(settings); - assertObjectEquals(expectedRequest, actionCodeSettings.buildRequest()); -} - - -function testActionCodeSettings_success_partialParameters_ios() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - 'iOS': { - 'bundleId': 'com.example.ios' - } - }; - var expectedRequest = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'canHandleCodeInApp': false - }; - var actionCodeSettings = new fireauth.ActionCodeSettings(settings); - assertObjectEquals(expectedRequest, actionCodeSettings.buildRequest()); -} - - -function testActionCodeSettings_success_partialParameters_ios_fdlDomain() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - 'iOS': { - 'bundleId': 'com.example.ios' - }, - 'dynamicLinkDomain': 'example.page.link' - }; - var expectedRequest = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'canHandleCodeInApp': false, - 'dynamicLinkDomain': 'example.page.link' - }; - var actionCodeSettings = new fireauth.ActionCodeSettings(settings); - assertObjectEquals(expectedRequest, actionCodeSettings.buildRequest()); -} - - -function testActionCodeSettings_error_continueUrl() { - // Missing continue URL. - assertActionCodeSettingsErrorThrown( - { - 'android': { - 'packageName': 'com.example.android' - } - }, - 'auth/missing-continue-uri'); - // Invalid continue URL. - assertActionCodeSettingsErrorThrown( - { - 'url': '', - 'android': { - 'packageName': 'com.example.android' - } - }, - 'auth/invalid-continue-uri'); - // Invalid continue URL. - assertActionCodeSettingsErrorThrown( - { - 'url': ['https://www.example.com/?state=abc'], - 'android': { - 'packageName': 'com.example.android' - } - }, - 'auth/invalid-continue-uri'); -} - - -function testActionCodeSettings_success_urlOnly_canHandleCodeInApp() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - 'handleCodeInApp': true - }; - var expectedRequest = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'canHandleCodeInApp': true - }; - var actionCodeSettings = new fireauth.ActionCodeSettings(settings); - assertObjectEquals(expectedRequest, actionCodeSettings.buildRequest()); -} - - -function testActionCodeSettings_error_canHandleCodeInApp() { - // Non-boolean can handle code in app. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'handleCodeInApp': 'false' - }, - 'auth/argument-error'); -} - - -function testActionCodeSettings_error_android() { - // Invalid Android field. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'android': 'bla' - }, - 'auth/argument-error'); - // Android package name set to empty string. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'android': { - 'packageName': '' - } - }, - 'auth/argument-error'); - // Android package missing when other Android parameters specified. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'android': { - 'installApp': true, - 'minimumVersion': '12' - } - }, - 'auth/missing-android-pkg-name'); - // Invalid Android package name. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'android': { - 'packageName': {} - } - }, - 'auth/argument-error'); - // Invalid installApp field. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'android': { - 'packageName': 'com.example.android', - 'installApp': 'bla' - } - }, - 'auth/argument-error'); - // Invalid minimumVersion field. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'android': { - 'packageName': 'com.example.android', - 'minimumVersion': false - } - }, - 'auth/argument-error'); -} - - -function testActionCodeSettings_error_ios() { - // Invalid iOS field. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'iOS': 'bla' - }, - 'auth/argument-error'); - // iOS bundle ID set to empty string. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'iOS': { - 'bundleId': '' - } - }, - 'auth/argument-error'); - // Invalid iOS bundle ID. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'iOS': { - 'bundleId': {} - } - }, - 'auth/argument-error'); -} - - -function testActionCodeSettings_error_dynamicLinkDomain() { - // Invalid dynamic link domain. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'dynamicLinkDomain': ['example.page.link'] - - }, - 'auth/argument-error'); - // Dynamic link domain set to empty string. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'dynamicLinkDomain': '' - }, - 'auth/argument-error'); - // Dynamic link domain set to null. - assertActionCodeSettingsErrorThrown( - { - 'url': 'https://www.example.com/?state=abc', - 'dynamicLinkDomain': null - }, - 'auth/argument-error'); -} diff --git a/packages/auth/test/actioncodeurl_test.js b/packages/auth/test/actioncodeurl_test.js deleted file mode 100644 index 12f6fc79d2f..00000000000 --- a/packages/auth/test/actioncodeurl_test.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for actioncodeurl.js. - */ - -goog.provide('fireauth.ActionCodeUrlTest'); - -goog.require('fireauth.ActionCodeInfo'); -goog.require('fireauth.ActionCodeURL'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly(); - - -function testActionCodeURL_success() { - var continueUrl = 'https://www.example.com/path/to/file?a=1&b=2#c=3'; - var actionLink = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=signIn&apiKey=API_KEY&' + - 'continueUrl=' + encodeURIComponent(continueUrl) + - '&languageCode=en&tenantId=TENANT_ID&state=bla'; - var actionCodeUrl = fireauth.ActionCodeURL.parseLink(actionLink); - assertEquals(fireauth.ActionCodeInfo.Operation.EMAIL_SIGNIN, - actionCodeUrl['operation']); - assertEquals('CODE', actionCodeUrl['code']); - assertEquals('API_KEY', actionCodeUrl['apiKey']); - // ContinueUrl should be decoded. - assertEquals(continueUrl, actionCodeUrl['continueUrl']); - assertEquals('TENANT_ID', actionCodeUrl['tenantId']); - assertEquals('en', actionCodeUrl['languageCode']); -} - - -function testActionCodeURL_getOperation() { - // EMAIL_SIGNIN email action. - var signInLink = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=signIn&apiKey=API_KEY&' + - 'languageCode=en'; - var actionCodeUrl = fireauth.ActionCodeURL.parseLink(signInLink); - assertEquals(fireauth.ActionCodeInfo.Operation.EMAIL_SIGNIN, - actionCodeUrl['operation']); - - // VERIFY_AND_CHANGE_EMAIL email action. - var verifyAndChangeEmailLink = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=verifyAndChangeEmail&apiKey=API_KEY&' + - 'languageCode=en'; - actionCodeUrl = fireauth.ActionCodeURL.parseLink(verifyAndChangeEmailLink); - assertEquals(fireauth.ActionCodeInfo.Operation.VERIFY_AND_CHANGE_EMAIL, - actionCodeUrl['operation']); - - // VERIFY_EMAIL email action. - var verifyEmailLink = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=verifyEmail&apiKey=API_KEY&' + - 'languageCode=en'; - actionCodeUrl = fireauth.ActionCodeURL.parseLink(verifyEmailLink); - assertEquals(fireauth.ActionCodeInfo.Operation.VERIFY_EMAIL, - actionCodeUrl['operation']); - - // RECOVER_EMAIL email action. - var recoverEmailLink = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=recoverEmail&apiKey=API_KEY&' + - 'languageCode=en'; - actionCodeUrl = fireauth.ActionCodeURL.parseLink(recoverEmailLink); - assertEquals(fireauth.ActionCodeInfo.Operation.RECOVER_EMAIL, - actionCodeUrl['operation']); - - // PASSWORD_RESET email action. - var resetPasswordLink = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=resetPassword&apiKey=API_KEY&' + - 'languageCode=en'; - actionCodeUrl = fireauth.ActionCodeURL.parseLink(resetPasswordLink); - assertEquals(fireauth.ActionCodeInfo.Operation.PASSWORD_RESET, - actionCodeUrl['operation']); - - // REVERT_SECOND_FACTOR_ADDITION email action. - var revertSeconFactorLink = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=revertSecondFactorAddition&apiKey=API_KEY&' + - 'languageCode=en'; - actionCodeUrl = fireauth.ActionCodeURL.parseLink(revertSeconFactorLink); - assertEquals(fireauth.ActionCodeInfo.Operation.REVERT_SECOND_FACTOR_ADDITION, - actionCodeUrl['operation']); -} - - -function testActionCodeURL_success_portNumberInUrl() { - var actionLink = 'https://www.example.com:8080/finishSignIn?' + - 'oobCode=CODE&mode=signIn&apiKey=API_KEY&state=bla'; - var actionCodeUrl = fireauth.ActionCodeURL.parseLink(actionLink); - assertEquals(fireauth.ActionCodeInfo.Operation.EMAIL_SIGNIN, - actionCodeUrl['operation']); - assertEquals('CODE', actionCodeUrl['code']); - assertEquals('API_KEY', actionCodeUrl['apiKey']); - assertNull(actionCodeUrl['continueUrl']); - assertNull(actionCodeUrl['tenantId']); - assertNull(actionCodeUrl['languageCode']); -} - - -function testActionCodeURL_success_hashParameters() { - var actionLink = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE1&mode=signIn&apiKey=API_KEY1&state=bla' + - '#oobCode=CODE2&mode=signIn&apiKey=API_KEY2&state=bla'; - var actionCodeUrl = fireauth.ActionCodeURL.parseLink(actionLink); - assertEquals(fireauth.ActionCodeInfo.Operation.EMAIL_SIGNIN, - actionCodeUrl['operation']); - assertEquals('CODE1', actionCodeUrl['code']); - assertEquals('API_KEY1', actionCodeUrl['apiKey']); - assertNull(actionCodeUrl['continueUrl']); - assertNull(actionCodeUrl['tenantId']); - assertNull(actionCodeUrl['languageCode']); -} - - -function testActionCodeURL_invalidLink() { - // Missing API key, oob code and mode. - var invalidLink1 = 'https://www.example.com/finishSignIn'; - // Invalid mode. - var invalidLink2 = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=INVALID_MODE&apiKey=API_KEY'; - // Missing oob code. - var invalidLink3 = 'https://www.example.com/finishSignIn?' + - 'mode=signIn&apiKey=API_KEY'; - // Missing API key. - var invalidLink4 = 'https://www.example.com/finishSignIn?' + - 'oobCode=CODE&mode=signIn'; - // Missing mode. - var invalidLink5 = 'https://www.example.com/finishSignIn' + - 'oobCode=CODE&apiKey=API_KEY'; - assertNull(fireauth.ActionCodeURL.parseLink(invalidLink1)); - assertNull(fireauth.ActionCodeURL.parseLink(invalidLink2)); - assertNull(fireauth.ActionCodeURL.parseLink(invalidLink3)); - assertNull(fireauth.ActionCodeURL.parseLink(invalidLink4)); - assertNull(fireauth.ActionCodeURL.parseLink(invalidLink5)); -} diff --git a/packages/auth/test/additionaluserinfo_test.js b/packages/auth/test/additionaluserinfo_test.js deleted file mode 100644 index d507aa604d7..00000000000 --- a/packages/auth/test/additionaluserinfo_test.js +++ /dev/null @@ -1,578 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for additionaluserinfo.js - */ - -goog.provide('fireauth.AdditionalUserInfoTest'); - -goog.require('fireauth.AdditionalUserInfo'); -goog.require('fireauth.FacebookAdditionalUserInfo'); -goog.require('fireauth.FederatedAdditionalUserInfo'); -goog.require('fireauth.GenericAdditionalUserInfo'); -goog.require('fireauth.GithubAdditionalUserInfo'); -goog.require('fireauth.GoogleAdditionalUserInfo'); -goog.require('fireauth.TwitterAdditionalUserInfo'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.AdditionalUserInfoTest'); - - -// iss: "https://securetoken.google.com/projectId" -// aud: "projectId" -// auth_time: 1506050282 -// user_id: "123456" -// sub: "123456" -// iat: 1506050283 -// exp: 1506053883 -// email: "user@example.com" -// email_verified: false -// phone_number: "+11234567890" -// firebase: {identities: {phone: ["+11234567890"], -// email: ["user@example.com"] -// }, sign_in_provider: "phone"} -var tokenPhone = 'HEAD.ew0KICAiaXNzIjogImh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLm' + - 'NvbS9wcm9qZWN0SWQiLA0KICAiYXVkIjogInByb2plY3RJZCIsDQogICJhdXRoX3RpbWUiOi' + - 'AxNTA2MDUwMjgyLA0KICAidXNlcl9pZCI6ICIxMjM0NTYiLA0KICAic3ViIjogIjEyMzQ1Ni' + - 'IsDQogICJpYXQiOiAxNTA2MDUwMjgzLA0KICAiZXhwIjogMTUwNjA1Mzg4MywNCiAgImVtYW' + - 'lsIjogInVzZXJAZXhhbXBsZS5jb20iLA0KICAiZW1haWxfdmVyaWZpZWQiOiBmYWxzZSwNCi' + - 'AgInBob25lX251bWJlciI6ICIrMTEyMzQ1Njc4OTAiLA0KICAiZmlyZWJhc2UiOiB7DQogIC' + - 'AgImlkZW50aXRpZXMiOiB7DQogICAgICAicGhvbmUiOiBbDQogICAgICAgICIrMTEyMzQ1Nj' + - 'c4OTAiDQogICAgICBdLA0KICAgICAgImVtYWlsIjogWw0KICAgICAgICAidXNlckBleGFtcG' + - 'xlLmNvbSINCiAgICAgIF0NCiAgICB9LA0KICAgICJzaWduX2luX3Byb3ZpZGVyIjogInBob2' + - '5lIg0KICB9DQp9.SIGNATURE'; - -// Typical verifyPhoneNumber response. -var verifyPhoneNumberResponse = { - 'idToken': tokenPhone, - 'refreshToken': 'REFRESH_TOKEN', - 'expiresIn': '3600', - 'localId': '123456', - 'isNewUser': true, - 'phoneNumber': '+11234567890' -}; - -// Expected generic additional user info object for the above verifyPhoneNumber -// response. -var expectedGenericAdditionalUserInfo = { - 'isNewUser': true, - 'providerId': 'phone' -}; - -// "iss": "https://securetoken.google.com/12345678", -// "picture": "https://plus.google.com/abcdefghijklmnopqrstu", -// "aud": "12345678", -// "auth_time": 1510357622, -// "user_id": "abcdefghijklmnopqrstu", -// "sub": "abcdefghijklmnopqrstu", -// "iat": 1510357622, -// "exp": 1510361222, -// "email": "user@example.com", -// "email_verified": true, -// "firebase": {"identities": { -// "email": ["user@example.com"] -// }, "sign_in_provider": "password"} -var tokenEmail = 'HEAD.ew0KICAiaXNzIjogImh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlL' + - 'mNvbS8xMjM0NTY3OCIsDQogICJwaWN0dXJlIjogImh0dHBzOi8vcGx1cy5' + - 'nb29nbGUuY29tL2FiY2RlZmdoaWprbG1ub3BxcnN0dSIsDQogICJhdWQiO' + - 'iAiMTIzNDU2NzgiLA0KICAiYXV0aF90aW1lIjogMTUxMDM1NzYyMiwNCiA' + - 'gInVzZXJfaWQiOiAiYWJjZGVmZ2hpamtsbW5vcHFyc3R1IiwNCiAgInN1Y' + - 'iI6ICJhYmNkZWZnaGlqa2xtbm9wcXJzdHUiLA0KICAiaWF0IjogMTUxMDM' + - '1NzYyMiwNCiAgImV4cCI6IDE1MTAzNjEyMjIsDQogICJlbWFpbCI6ICJ1c' + - '2VyQGV4YW1wbGUuY29tIiwNCiAgImVtYWlsX3ZlcmlmaWVkIjogdHJ1ZSw' + - 'NCiAgImZpcmViYXNlIjogew0KICAgICJpZGVudGl0aWVzIjogew0KICAgI' + - 'CAgImVtYWlsIjogWw0KICAgICAgICAidXNlckBleGFtcGxlLmNvbSINCiA' + - 'gICAgIF0NCiAgICB9LA0KICAgICJzaWduX2luX3Byb3ZpZGVyIjogInBhc' + - '3N3b3JkIg0KICB9DQp9.SIGNATURE'; - -// SignupNewUserResponse response without isNewUser field. -var signUpNewUserResponse = { - 'kind': 'identitytoolkit#SignupNewUserResponse', - 'idToken': tokenEmail, - 'refreshToken': 'REFRESH_TOKEN', - 'expiresIn': '3600', - 'localId': '123456' -}; - -// Expected generic additional user info object for the above signUpNewUser -// response. -var expectedGenericAdditionalUserInfoForSignUpNewUser = { - 'isNewUser': true, - 'providerId': 'password' -}; - -// Typical minimal verifyAssertion response for generic IdP user with no profile -// data. -var noProfileVerifyAssertion = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'idToken': 'ID_TOKEN', - 'isNewUser': true, - 'providerId': 'noprofile.com' -}; - -// Expected federated additional user info object with no profile. -var expectedNoProfileAdditionalUserInfo = { - 'isNewUser': true, - 'providerId': 'noprofile.com', - 'profile' : {} -}; - -// Typical verifyAssertion response for google user. -var googleVerifyAssertion = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'isNewUser': true, - 'idToken': 'ID_TOKEN', - 'providerId': 'google.com', - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + - 'me":{"givenName":"John","familyName":"Doe"},"language":"en","isPl' + - 'usUser":true,"url":"https://plus.google.com/abcdefghijklmnopqrstu' + - '","image":{"url":"https://lh5.googleusercontent.com/123456789012/' + - 'abcdefghijklmnopqrstuvwxyz/12345678/photo.jpg?sz=50","isDefault":' + - 'false},"placesLived":[{"primary":true,"value":"Mountain View, CA"' + - '}],"emails":[{"type":"account","value":"dummyuser1234567@gmail.co' + - 'm"}],"ageRange":{"min":21},"verified":false,"circledByCount":0,"i' + - 'd":"abcdefghijklmnopqrstu","objectType":"person"}' -}; - -// Expected Google additional user info object. -var expectedGoogleAdditionalUserInfo = { - 'providerId': 'google.com', - 'isNewUser': true, - 'profile' : { - 'kind': 'plus#person', - 'displayName': 'John Doe', - 'name': { - 'givenName': 'John', - 'familyName': 'Doe' - }, - 'language': 'en', - 'isPlusUser': true, - 'url': 'https://plus.google.com/abcdefghijklmnopqrstu', - 'image': { - 'url': 'https://lh5.googleusercontent.com/123456789012/abcdefghijklmno' + - 'pqrstuvwxyz/12345678/photo.jpg?sz=50', - 'isDefault': false - }, - 'placesLived': [ - { - 'primary': true, - 'value': 'Mountain View, CA' - } - ], - 'emails': [ - { - 'type': 'account', - 'value': 'dummyuser1234567@gmail.com' - } - ], - 'ageRange': { - 'min': 21 - }, - 'verified': false, - 'circledByCount': 0, - 'id': 'abcdefghijklmnopqrstu', - 'objectType': 'person' - } -}; - -// Typical verifyAssertion response for Facebook user. -var facebookVerifyAssertion = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'isNewUser': true, - 'idToken': 'ID_TOKEN', - 'providerId': 'facebook.com', - 'rawUserInfo': '{"updated_time":"2015-09-14T19:51:07+0000","gender' + - '":"male","timezone":-7,"link":"https://www.facebook.com/abcdefghi' + - 'jklmnopqr/1234567890123456/","verified":true,"last_name":"Do","loc' + - 'ale":"en_US","picture":{"data":{"is_silhouette":true,"url":"https' + - '://scontent.xx.fbcdn.net/v.jpg"}},"age_range":{"min":21},"name":"' + - 'John Do","id":"1234567890123456","first_name":"John","email":"dumm' + - 'yuser1234567@gmail.com"}' -}; - -// Expected Facebook additional user info object. -var expectedFacebookAdditionalUserInfo = { - 'providerId': 'facebook.com', - 'isNewUser': true, - 'profile' : { - 'updated_time': '2015-09-14T19:51:07+0000', - 'gender': 'male', - 'timezone': -7, - 'link': 'https://www.facebook.com/abcdefghijklmnopqr/1234567890123456/', - 'verified': true, - 'last_name': 'Do', - 'locale': 'en_US', - 'picture': { - 'data': { - 'is_silhouette': true, - 'url': 'https://scontent.xx.fbcdn.net/v.jpg' - } - }, - 'age_range': { - 'min': 21 - }, - 'name': 'John Do', - 'id': '1234567890123456', - 'first_name': 'John', - 'email': 'dummyuser1234567@gmail.com' - } -}; - -// Typical verifyAssertion response for Twitter user. -var twitterVerifyAssertion = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'isNewUser': false, - 'idToken': 'ID_TOKEN', - 'providerId': 'twitter.com', - 'screenName': 'twitterxy', - 'rawUserInfo': '{"utc_offset":null,"friends_count":10,"profile_ima' + - 'ge_url_https":"https://abs.twimg.com/sticky/default_profile_image' + - 's/default_profile_3_normal.png","listed_count":0,"profile_backgro' + - 'und_image_url":"http://abs.twimg.com/images/themes/theme1/bg.png"' + - ',"default_profile_image":true,"favourites_count":0,"description":' + - '"","created_at":"Thu Mar 26 03:05:49 +0000 2015","is_translator":' + - 'false,"profile_background_image_url_https":"https://abs.twimg.com' + - '/images/themes/theme1/bg.png","protected":false,"screen_name":"tw' + - 'itterxy","id_str":"1234567890","profile_link_color":"0084B4","is_' + - 'translation_enabled":false,"id":1234567890,"geo_enabled":false,"p' + - 'rofile_background_color":"C0DEED","lang":"en","has_extended_profi' + - 'le":false,"profile_sidebar_border_color":"C0DEED","profile_text_c' + - 'olor":"333333","verified":false,"profile_image_url":"http://abs.t' + - 'wimg.com/sticky/default_profile_images/default_profile_3_normal.p' + - 'ng","time_zone":null,"url":null,"contributors_enabled":false,"pro' + - 'file_background_tile":false,"entities":{"description":{"urls":[]}' + - '},"statuses_count":0,"follow_request_sent":false,"followers_count":' + - '1,"profile_use_background_image":true,"default_profile":true,"follo' + - 'wing":false,"name":"John Doe","location":"","profile_sidebar_fill_c' + - 'olor":"DDEEF6","notifications":false}' -}; - -// Expected Twitter additional user info object. -var expectedTwitterAdditionalUserInfo = { - 'isNewUser': false, - 'providerId': 'twitter.com', - 'username': 'twitterxy', - 'profile' : { - 'utc_offset': null, - 'friends_count': 10, - 'profile_image_url_https': 'https://abs.twimg.com/sticky/default_profile' + - '_images/default_profile_3_normal.png', - 'listed_count': 0, - 'profile_background_image_url': 'http://abs.twimg.com/images/themes/them' + - 'e1/bg.png', - 'default_profile_image': true, - 'favourites_count': 0, - 'description': '', - 'created_at': 'Thu Mar 26 03:05:49 +0000 2015', - 'is_translator': false, - 'profile_background_image_url_https': 'https://abs.twimg.com/images/them' + - 'es/theme1/bg.png', - 'protected': false, - 'screen_name': 'twitterxy', - 'id_str': '1234567890', - 'profile_link_color': '0084B4', - 'is_translation_enabled': false, - 'id': 1234567890, - 'geo_enabled': false, - 'profile_background_color': 'C0DEED', - 'lang': 'en', - 'has_extended_profile': false, - 'profile_sidebar_border_color': 'C0DEED', - 'profile_text_color': '333333', - 'verified': false, - 'profile_image_url': 'http://abs.twimg.com/sticky/default_profile_images' + - '/default_profile_3_normal.png', - 'time_zone': null, - 'url': null, - 'contributors_enabled': false, - 'profile_background_tile': false, - 'entities': { - 'description': { - 'urls': [] - } - }, - 'statuses_count': 0, - 'follow_request_sent': false, - 'followers_count': 1, - 'profile_use_background_image': true, - 'default_profile': true, - 'following': false, - 'name': 'John Doe', - 'location': '', - 'profile_sidebar_fill_color': 'DDEEF6', - 'notifications': false - } -}; - -// Typical verifyAssertion response for GitHub user. -var githubVerifyAssertion = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'idToken': 'ID_TOKEN', - 'isNewUser': false, - 'providerId': 'github.com', - 'rawUserInfo': '{"gists_url":"https://api.github.com/users/uid1234' + - '567890/gists{/gist_id}","repos_url":"https://api.github.com/users' + - '/uid1234567890/repos","following_url":"https://api.github.com/use' + - 'rs/uid1234567890/following{/other_user}","bio":null,"created_at":' + - '"2015-07-23T21:49:36Z","login":"uid1234567890","type":"User","blo' + - 'g":null,"subscriptions_url":"https://api.github.com/users/uid1234' + - '567890/subscriptions","updated_at":"2016-06-21T20:22:45Z","site_a' + - 'dmin":false,"company":null,"id":13474811,"public_repos":0,"gravat' + - 'ar_id":"","email":null,"organizations_url":"https://api.github.co' + - 'm/users/uid1234567890/orgs","hireable":null,"starred_url":"https:' + - '//api.github.com/users/uid1234567890/starred{/owner}{/repo}","fol' + - 'lowers_url":"https://api.github.com/users/uid1234567890/followers' + - '","public_gists":0,"url":"https://api.github.com/users/uid1234567' + - '890","received_events_url":"https://api.github.com/users/uid12345' + - '67890/received_events","followers":0,"avatar_url":"https://avatar' + - 's.githubusercontent.com/u/12345678?v\\u003d3","events_url":"https' + - '://api.github.com/users/uid1234567890/events{/privacy}","html_url' + - '":"https://github.com/uid1234567890","following":0,"name":null,"l' + - 'ocation":null}' -}; - -// Expected GitHub additional user info object. -var expectedGithubAdditionalUserInfo = { - 'isNewUser': false, - 'providerId': 'github.com', - 'username': 'uid1234567890', - 'profile' : { - 'gists_url': 'https://api.github.com/users/uid1234567890/gists{/gist_id}', - 'repos_url': 'https://api.github.com/users/uid1234567890/repos', - 'following_url': 'https://api.github.com/users/uid1234567890/following{/' + - 'other_user}', - 'bio': null, - 'created_at': '2015-07-23T21:49:36Z', - 'login': 'uid1234567890', - 'type': 'User', - 'blog': null, - 'subscriptions_url':'https://api.github.com/users/uid1234567890/subscrip' + - 'tions', - 'updated_at': '2016-06-21T20:22:45Z', - 'site_admin': false, - 'company': null, - 'id': 13474811, - 'public_repos': 0, - 'gravatar_id': '', - 'email': null, - 'organizations_url': 'https://api.github.com/users/uid1234567890/orgs', - 'hireable': null, - 'starred_url': 'https://api.github.com/users/uid1234567890/starred{/owne' + - 'r}{/repo}', - 'followers_url': 'https://api.github.com/users/uid1234567890/followers', - 'public_gists': 0, - 'url': 'https://api.github.com/users/uid1234567890', - 'received_events_url': 'https://api.github.com/users/uid1234567890/recei' + - 'ved_events', - 'followers': 0, - 'avatar_url': 'https://avatars.githubusercontent.com/u/12345678?v=3', - 'events_url': 'https://api.github.com/users/uid1234567890/events{/privacy}', - 'html_url': 'https://github.com/uid1234567890', - 'following': 0, - 'name': null, - 'location': null - } -}; - - -function testInvalidAdditionalUserInfo() { - var invalid = {}; - try { - new fireauth.FederatedAdditionalUserInfo(invalid); - fail('Initializing an invalid additional user info object should fail.'); - } catch (e) { - assertEquals('Invalid additional user info!', e.message); - } - assertNull(fireauth.AdditionalUserInfo.fromPlainObject(invalid)); -} - - -function testGenericAdditionalUserInfo() { - var genericAdditionalUserInfo = new fireauth.GenericAdditionalUserInfo( - verifyPhoneNumberResponse); - assertObjectEquals( - expectedGenericAdditionalUserInfo, - genericAdditionalUserInfo); - assertObjectEquals( - genericAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(verifyPhoneNumberResponse)); -} - - - -function testGenericAdditionalUserInfo_fromSignUpNewUserResponse() { - var genericAdditionalUserInfo = new fireauth.GenericAdditionalUserInfo( - signUpNewUserResponse); - assertObjectEquals( - expectedGenericAdditionalUserInfoForSignUpNewUser, - genericAdditionalUserInfo); - assertObjectEquals( - genericAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(signUpNewUserResponse)); -} - - -function testGenericAdditionalUserInfo_fromAnonymousSignInResponse() { - // "iss": "https://securetoken.google.com/12345678", - // "provider_id": "anonymous", - // "aud": "12345678", - // "auth_time": 1510874749, - // "user_id": "abcdefghijklmnopqrstu", - // "sub": "abcdefghijklmnopqrstu", - // "iat": 1510874749, - // "exp": 1510878349, - // "firebase": { "identities": {}, - // "sign_in_provider": "anonymous"} - var tokenAnonymous = 'HEAD.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5' + - 'jb20vMTIzNDU2NzgiLCJwcm92aWRlcl9pZCI6ImFub255bW91cyI' + - 'sImF1ZCI6IjEyMzQ1Njc4IiwiYXV0aF90aW1lIjoxNTEwODc0NzQ' + - '5LCJ1c2VyX2lkIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1Iiwic3V' + - 'iIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1IiwiaWF0IjoxNTEwODc' + - '0NzQ5LCJleHAiOjE1MTA4NzgzNDksImZpcmViYXNlIjp7ImlkZW5' + - '0aXRpZXMiOnt9LCJzaWduX2luX3Byb3ZpZGVyIjoiYW5vbnltb3V' + - 'zIn0sImFsZyI6IkhTMjU2In0.SIGNATURE'; - var anonymousSignInResponse = { - 'kind': 'identitytoolkit#SignupNewUserResponse', - 'idToken': tokenAnonymous, - 'refreshToken': 'REFRESH_TOKEN', - 'expiresIn': '3600', - 'localId': '123456' - }; - var expectedGenericAdditionalUserInfo = { - 'isNewUser': true, - 'providerId': null - }; - var genericAdditionalUserInfo = new fireauth.GenericAdditionalUserInfo( - anonymousSignInResponse); - assertObjectEquals( - expectedGenericAdditionalUserInfo, - genericAdditionalUserInfo); - assertObjectEquals( - genericAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(anonymousSignInResponse)); - -} - - -function testGenericAdditionalUserInfo_fromCustomTokenSignInResponse() { - // "iss": "https://securetoken.google.com/12345678", - // "aud": "12345678", - // "auth_time": 1511378629, - // "user_id": "abcdefghijklmnopqrstu", - // "sub": "abcdefghijklmnopqrstu", - // "iat": 1511378630, - // "exp": 1511382230, - // "firebase": { "identities": {}, - // "sign_in_provider": "custom"} - var tokenCustom = 'HEAD.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb2' + - '0vMTIzNDU2NzgiLCJhdWQiOiIxMjM0NTY3OCIsImF1dGhfdGltZSI6M' + - 'TUxMTM3ODYyOSwidXNlcl9pZCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0' + - 'dSIsInN1YiI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dSIsImlhdCI6MTU' + - 'xMTM3ODYzMCwiZXhwIjoxNTExMzgyMjMwLCJmaXJlYmFzZSI6eyJpZG' + - 'VudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImN1c3RvbSJ9L' + - 'CJhbGciOiJIUzI1NiJ9.SIGNATURE'; - var customTokenSignInResponse = { - 'kind': 'identitytoolkit#VerifyCustomTokenResponse', - 'idToken': tokenCustom, - 'refreshToken': 'REFRESH_TOKEN', - 'expiresIn': '3600', - 'localId': '123456' - }; - var expectedGenericAdditionalUserInfo = { - 'isNewUser': false, - 'providerId': null - }; - var genericAdditionalUserInfo = new fireauth.GenericAdditionalUserInfo( - customTokenSignInResponse); - assertObjectEquals( - expectedGenericAdditionalUserInfo, - genericAdditionalUserInfo); - assertObjectEquals( - genericAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(customTokenSignInResponse)); - -} - - -function testFederatedAdditionalUserInfo_withProfile() { - var federatedAdditionalUserInfo = - new fireauth.FederatedAdditionalUserInfo(facebookVerifyAssertion); - assertObjectEquals( - expectedFacebookAdditionalUserInfo, - federatedAdditionalUserInfo); -} - - -function testFederatedAdditionalUserInfo_noProfile() { - var noProfileAdditionalUserInfo = - new fireauth.FederatedAdditionalUserInfo(noProfileVerifyAssertion); - assertObjectEquals( - expectedNoProfileAdditionalUserInfo, - noProfileAdditionalUserInfo); - assertObjectEquals( - noProfileAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(noProfileVerifyAssertion)); -} - - -function testFacebookAdditionalUserInfo() { - var facebookAdditionalUserInfo = - new fireauth.FacebookAdditionalUserInfo(facebookVerifyAssertion); - assertObjectEquals( - expectedFacebookAdditionalUserInfo, - facebookAdditionalUserInfo); - assertObjectEquals( - facebookAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(facebookVerifyAssertion)); -} - - -function testGithubAdditionalUserInfo() { - var githubAdditionalUserInfo = - new fireauth.GithubAdditionalUserInfo(githubVerifyAssertion); - assertObjectEquals( - expectedGithubAdditionalUserInfo, - githubAdditionalUserInfo); - assertObjectEquals( - githubAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(githubVerifyAssertion)); -} - - -function testGoogleAdditionalUserInfo() { - var googleAdditionalUserInfo = - new fireauth.GoogleAdditionalUserInfo(googleVerifyAssertion); - assertObjectEquals( - expectedGoogleAdditionalUserInfo, - googleAdditionalUserInfo); - assertObjectEquals( - googleAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(googleVerifyAssertion)); -} - - -function testTwitterAdditionalUserInfo() { - var twitterAdditionalUserInfo = - new fireauth.TwitterAdditionalUserInfo(twitterVerifyAssertion); - assertObjectEquals( - expectedTwitterAdditionalUserInfo, - twitterAdditionalUserInfo); - assertObjectEquals( - twitterAdditionalUserInfo, - fireauth.AdditionalUserInfo.fromPlainObject(twitterVerifyAssertion)); -} diff --git a/packages/auth/test/args_test.js b/packages/auth/test/args_test.js deleted file mode 100644 index e00b6cc3e46..00000000000 --- a/packages/auth/test/args_test.js +++ /dev/null @@ -1,914 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.argsTest'); - -goog.require('fireauth.Auth'); -goog.require('fireauth.AuthUser'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.PhoneMultiFactorGenerator'); -goog.require('fireauth.args'); -goog.require('goog.Promise'); -goog.require('goog.dom'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.argsTest'); - - -var app = firebase.initializeApp({ - apiKey: 'myApiKey' -}); -var auth = new fireauth.Auth(app); -var tokenResponse = { - 'idToken': 'accessToken', - 'refreshToken': 'refreshToken', - 'expiresIn': 3600 -}; -var user = new fireauth.AuthUser( - { - apiKey: 'myApiKey', - appName: 'appId' - }, - tokenResponse); - - -function testValidate_valid_noArgs() { - fireauth.args.validate('myFunc', [], []); -} - - -function testValidate_valid_oneArg() { - fireauth.args.validate('myFunc', [fireauth.args.string('foo')], ['bar']); -} - - -function testValidate_valid_multipleArgs() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.bool('emailVerified') - ]; - var args = ['foo@bar.com', false]; - fireauth.args.validate('myFunc', expectedArgs, args); -} - - -function testValidate_valid_optionalArgs_absent() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.bool('emailVerified', true) - ]; - var args = ['foo@bar.com']; - fireauth.args.validate('myFunc', expectedArgs, args); -} - - -function testValidate_valid_optionalArgs_present() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.bool('emailVerified', true) - ]; - var args = ['foo@bar.com', true]; - fireauth.args.validate('myFunc', expectedArgs, args); -} - - -function testValidate_invalid_type() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [fireauth.args.string('name')], [13]); - }); - assertEquals('auth/argument-error', error.code); - assertEquals('myFunc failed: First argument "name" must be a valid ' + - 'string.', error.message); -} - - -function testValidate_invalid_isSetter() { - var error = assertThrows(function() { - fireauth.args.validate( - 'name', [fireauth.args.string('name')], [13], true); - }); - assertEquals('auth/argument-error', error.code); - assertEquals('name failed: "name" must be a valid string.', error.message); -} - - -function testValidate_invalid_onlyOneValid() { - var error = assertThrows(function() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.string('password') - ]; - var args = ['foo@bar.com', 13]; - fireauth.args.validate('yourFunc', expectedArgs, args); - }); - assertEquals('yourFunc failed: Second argument "password" must be a valid ' + - 'string.', error.message); -} - - -function testValidate_invalid_noname() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [fireauth.args.string()], [13]); - }); - assertEquals('auth/argument-error', error.code); - assertEquals('myFunc failed: First argument must be a valid ' + - 'string.', error.message); -} - - -function testValidate_argNumber_tooFew() { - var error = assertThrows(function() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.string('password') - ]; - var args = ['foo@bar.com']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: Expected 2 arguments but got 1.', - error.message); -} - - -function testValidate_argNumber_tooMany() { - var error = assertThrows(function() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.string('password') - ]; - var args = ['foo@bar.com', 'hunter2', 'whatamidoinghere']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: Expected 2 arguments but got 3.', - error.message); -} - - -function testValidate_argNumber_oneValid() { - var error = assertThrows(function() { - var expectedArgs = [ - fireauth.args.string('email') - ]; - var args = ['foo@bar.com', 'whatamidoinghere']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: Expected 1 argument but got 2.', - error.message); -} - - -function testValidate_argNumber_validRange() { - var error = assertThrows(function() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.string('password', true) - ]; - var args = ['foo@bar.com', 'hunter2', 'whatamidoinghere']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: Expected 1-2 arguments but got 3.', - error.message); -} - - -function testValidate_authCredential_valid() { - var expectedArgs = [fireauth.args.authCredential()]; - var args = [fireauth.GoogleAuthProvider.credential('foo')]; - fireauth.args.validate('myFunc', expectedArgs, args); -} - - -function testValidate_authCredential_invalid() { - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.authCredential()]; - var args = ['foo']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "credential" must be a valid ' + - 'credential.', error.message); -} - - -function testValidate_authCredential_null() { - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.authCredential()]; - var args = [null]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "credential" must be a valid ' + - 'credential.', error.message); -} - - -function testValidate_authCredential_withType_valid() { - var expectedArgs = [fireauth.args.authCredential('phone')]; - var args = [fireauth.PhoneAuthProvider.credential('id', 'code')]; - fireauth.args.validate('myFunc', expectedArgs, args); -} - - -function testValidate_authCredential_withType_invalid() { - var expectedMessage = 'myFunc failed: First argument "phoneCredential" ' + - 'must be a valid phone credential.'; - var expectedArgs = [fireauth.args.authCredential('phone')]; - var error = assertThrows(function() { - var args = [fireauth.GoogleAuthProvider.credential('foo')]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals(expectedMessage, error.message); - - error = assertThrows(function() { - var args = [ - fireauth.EmailAuthProvider.credential('me@example.com', '123123') - ]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals(expectedMessage, error.message); - - error = assertThrows(function() { - var args = ['I am the wrong type']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals(expectedMessage, error.message); -} - - -function testValidate_authCredential_withTypeAndName_invalid() { - var expectedMessage = 'myFunc failed: First argument "myArgName" ' + - 'must be a valid google credential.'; - var expectedArgs = [fireauth.args.authCredential('google', 'myArgName')]; - var error = assertThrows(function() { - var args = [fireauth.GoogleAuthProvider.credential('foo')]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals(expectedMessage, error.message); -} - - -function testValidate_authProvider_valid() { - var expectedArgs = [fireauth.args.authProvider()]; - var args = [new fireauth.GoogleAuthProvider()]; - fireauth.args.validate('myFunc', expectedArgs, args); - // Test with email and password Auth provider. - args = [new fireauth.EmailAuthProvider()]; - fireauth.args.validate('myFunc', expectedArgs, args); -} - - -function testValidate_authProvider_invalid() { - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.authProvider()]; - var args = [fireauth.GoogleAuthProvider.credential('thisisntaprovider')]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "authProvider" must be a valid ' + - 'Auth provider.', error.message); -} - - -function testValidate_multiFactorInfo_valid() { - var expectedArgs = [fireauth.args.multiFactorInfo()]; - // Test with a valid MulfiFactorInfo object. - var args = [fireauth.MultiFactorInfo.fromPlainObject({ - 'uid': 'ENROLLMENT_ID', - 'displayName': 'My Phone', - 'enrollmentTime': 'Thu, 18 Apr 2019 18:38:12 GMT', - 'phoneNumber': '+16505551234' - })]; - assertNotThrows(function() { - fireauth.args.validate('myFunc', expectedArgs, args); - }); -} - - -function testValidate_multiFactorInfo_invalid() { - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.multiFactorInfo()]; - var args = [fireauth.MultiFactorInfo.fromPlainObject({ - 'uid': null, - 'displayName': 'My Phone', - 'enrollmentTime': 'Thu, 18 Apr 2019 18:38:12 GMT', - 'phoneNumber': '+16505551234' - })]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "multiFactorInfo" must be a ' + - 'valid multiFactorInfo.', error.message); -} - - -function testValidate_phoneInfoOptions_valid() { - // Test phoneInfoOptions for single-factor sign-in. - var expectedArgs = [fireauth.args.phoneInfoOptions()]; - var args = [{'phoneNumber': '+16505551234'}]; - assertNotThrows(function() { - fireauth.args.validate('myFunc', expectedArgs, args); - }); - - // Test phoneInfoOptions for multi-factor enrollment. - args = [{ - 'phoneNumber': '+16505551234', - 'session': { - 'type': 'enroll', - 'getRawSession': function() { - return goog.Promise.resolve('SESSION'); - } - } - }]; - assertNotThrows(function() { - fireauth.args.validate('myFunc', expectedArgs, args); - }); - - // Test phoneInfoOptions for multi-factor sign-in with multi-factor hint. - args = [{ - 'multiFactorHint': { - 'uid': 'ENROLLMENT_ID' - }, - 'session': { - 'type': 'signin', - 'getRawSession': function() { - return goog.Promise.resolve('SESSION'); - } - } - }]; - assertNotThrows(function() { - fireauth.args.validate('myFunc', expectedArgs, args); - }); - - // Test phoneInfoOptions for multi-factor sign-in with multi-factor UID. - args = [{ - 'multiFactorUid': 'ENROLLMENT_ID', - 'session': { - 'type': 'signin', - 'getRawSession': function() { - return goog.Promise.resolve('SESSION'); - } - } - }]; - assertNotThrows(function() { - fireauth.args.validate('myFunc', expectedArgs, args); - }); -} - - -function testValidate_phoneInfoOptions_enrollWithPhoneHint() { - // Test that session is for enrollment but multi-factor hint is provided. - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.phoneInfoOptions()]; - var args = [{ - 'multiFactorHint': {'uid': 'ENROLLMENT_ID'}, - 'session': { - 'type': 'enroll', - 'getRawSession': function() { - return goog.Promise.resolve('SESSION'); - } - } - }]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "phoneInfoOptions" must be ' + - 'valid phone info options.', error.message); -} - - -function testValidate_phoneInfoOptions_signInWithPhoneNumber() { - // Test that session is for sign-in but phone number is provided. - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.phoneInfoOptions()]; - var args = [{ - 'phoneNumber': '+16505551234', - 'session': { - 'type': 'signin', - 'getRawSession': function() { - return goog.Promise.resolve('SESSION'); - } - } - }]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "phoneInfoOptions" must be ' + - 'valid phone info options.', error.message); -} - - -function testValidate_phoneInfoOptions_missingSession() { - // Test that session is missing for multi-factor sign-in. - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.phoneInfoOptions()]; - var args = [{ - 'multiFactorHint': {'uid': 'ENROLLMENT_ID'} - }]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "phoneInfoOptions" must be ' + - 'valid phone info options.', error.message); -} - - -function testValidate_phoneInfoOptions_invalidSession() { - // Test with invalid multi-factor session. - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.phoneInfoOptions()]; - var args = [{ - 'phoneNumber': '+16505551234', - // Missing getRawSession. - 'session': { - 'type': 'enroll' - } - }]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "phoneInfoOptions" must be ' + - 'valid phone info options.', error.message); -} - - -function testValidate_phoneInfoOptions_invalidHint() { - // Test with invalid multi-factor hint. - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.phoneInfoOptions()]; - var args = [{ - // Missing uid. - 'multiFactorHint': { - }, - 'session': { - 'type': 'signin', - 'getRawSession': function() { - return goog.Promise.resolve('SESSION'); - } - } - }]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "phoneInfoOptions" must be ' + - 'valid phone info options.', error.message); -} - - -function testValidate_phoneInfoOptions_invalidUid() { - // Test with invalid multi-factor UID. - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.phoneInfoOptions()]; - var args = [{ - // Invalid UID. - 'multiFactorUid': 1234567890, - 'session': { - 'type': 'signin', - 'getRawSession': function() { - return goog.Promise.resolve('SESSION'); - } - } - }]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('myFunc failed: First argument "phoneInfoOptions" must be ' + - 'valid phone info options.', error.message); -} - - -function testValidate_or_valid_first() { - fireauth.args.validate('myFunc', [ - fireauth.args.or(fireauth.args.string(), fireauth.args.bool()) - ], ['foo']); -} - - -function testValidate_or_valid_second() { - fireauth.args.validate('myFunc', [ - fireauth.args.or(fireauth.args.string(), fireauth.args.bool()) - ], [true]); -} - - -function testValidate_or_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.or(fireauth.args.string(), fireauth.args.bool(), - 'strOrBool') - ], [13]); - }); - assertEquals('auth/argument-error', error.code); - assertEquals('myFunc failed: First argument "strOrBool" must be a valid ' + - 'string or a boolean.', error.message); -} - - -function testValidate_or_nested_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.or( - fireauth.args.or(fireauth.args.string(), fireauth.args.null()), - fireauth.args.bool(), - 'strOrBoolOrNull') - ], [true]); -} - - -function testValidate_or_nested_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.or( - fireauth.args.or(fireauth.args.string(), fireauth.args.bool()), - fireauth.args.null(), - 'strOrBoolOrNull') - ], [5]); - }); - assertEquals('myFunc failed: First argument "strOrBoolOrNull" must be a ' + - 'valid string or a boolean or null.', error.message); -} - - -function testValidate_or_nested_invalid_secondArgNested() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.or( - fireauth.args.string(), - fireauth.args.or(fireauth.args.bool(), fireauth.args.null()), - 'strOrBoolOrNull') - ], [5]); - }); - assertEquals('myFunc failed: First argument "strOrBoolOrNull" must be a ' + - 'valid string or a boolean or null.', error.message); -} - - -function testValidate_number_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.number('myNumber') - ], [-12.4]); -} - - -function testValidate_number_valid_optional() { - fireauth.args.validate('myFunc', [ - fireauth.args.number('myNumber', true) - ], []); -} - - -function testValidate_number_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.number('myNumber') - ], ['13']); - }); - assertEquals('myFunc failed: First argument "myNumber" must be a valid ' + - 'number.', error.message); -} - - -function testValidate_object_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.object('myObject') - ], [{'foo': 1}]); -} - - -function testValidate_object_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.object('myObject') - ], [13]); - }); - assertEquals('myFunc failed: First argument "myObject" must be a valid ' + - 'object.', error.message); -} - - -function testValidate_function_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.func('myCallback') - ], [function() {}]); -} - - -function testValidate_function_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.func('myCallback') - ], [{}]); - }); - assertEquals('myFunc failed: First argument "myCallback" must be a ' + - 'function.', error.message); -} - - -function testValidate_null_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.null() - ], [null]); -} - - -function testValidate_null_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.null('noArg') - ], ['something']); - }); - assertEquals('myFunc failed: First argument "noArg" must be null.', - error.message); -} - - -function testValidate_firebaseAuth_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.firebaseAuth() - ], [auth]); -} - - -function testValidate_firebaseAuth_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.firebaseAuth() - ], [{'some': 'thing'}]); - }); - assertEquals('myFunc failed: First argument "auth" must be an instance of ' + - 'Firebase Auth.', error.message); -} - - -function testValidate_firebaseUser_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.firebaseUser() - ], [user]); -} - - -function testValidate_firebaseUser_valid_optional() { - fireauth.args.validate('myFunc', [ - fireauth.args.firebaseUser(true) - ], []); -} - - -function testValidate_firebaseUser_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.firebaseUser() - ], [{'some': 'thing'}]); - }); - assertEquals('myFunc failed: First argument "user" must be an instance of ' + - 'Firebase User.', error.message); -} - - -function testValidate_element_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.element('myElement') - ], [goog.dom.createDom(goog.dom.TagName.DIV)]); -} - - -function testValidate_element_valid_optional() { - fireauth.args.validate('myFunc', [ - fireauth.args.element('myElement', true) - ], []); -} - - -function testValidate_element_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.element('myElement') - ], [13]); - }); - assertEquals('myFunc failed: First argument "myElement" must be an HTML ' + - 'element.', error.message); -} - - -function testValidate_firebaseApp_valid() { - fireauth.args.validate('myFunc', [ - fireauth.args.firebaseApp() - ], [app]); -} - - -function testValidate_firebaseApp_valid_optional() { - fireauth.args.validate('myFunc', [ - fireauth.args.firebaseApp(true) - ], []); -} - - -function testValidate_firebaseApp_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.firebaseApp() - ], [{'some': 'thing'}]); - }); - assertEquals('myFunc failed: First argument "app" must be an instance of ' + - 'Firebase App.', error.message); -} - - -function testValidate_appVerifier_valid() { - var appVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve('assertion'); - } - }; - fireauth.args.validate('myFunc', [ - fireauth.args.applicationVerifier() - ], [appVerifier]); -} - - -function testValidate_appVerifier_invalid() { - var error = assertThrows(function() { - fireauth.args.validate('myFunc', [ - fireauth.args.applicationVerifier() - ], [{'some': 'thing'}]); - }); - assertEquals('myFunc failed: First argument "applicationVerifier" must be ' + - 'an implementation of firebase.auth.ApplicationVerifier.', error.message); -} - - -function testValidate_requiredArgAfterOptional() { - var error = assertThrows(function() { - var expectedArgs = [ - fireauth.args.string('email', true), - fireauth.args.string('password'), - fireauth.args.string('emailVerified', true) - ]; - var args = ['foo@bar.com', 'hunter2', false]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('auth/internal-error', error.code); -} - - -function testValidate_optionalUndefined_single() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.string('password'), - fireauth.args.bool('emailVerified', true) - ]; - // Optional valid parameter explicitly passed. - var args = ['foo@bar.com', 'hunter2', false]; - fireauth.args.validate('myFunc', expectedArgs, args); - // Optional valid parameter not passed. - args = ['foo@bar.com', 'hunter2']; - fireauth.args.validate('myFunc', expectedArgs, args); - // Optional valid parameter passed as undefined. - args = ['foo@bar.com', 'hunter2', undefined]; - fireauth.args.validate('myFunc', expectedArgs, args); - // Optional parameter passed as invalid. - var error = assertThrows(function() { - args = ['foo@bar.com', 'hunter2', 'invalid']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('auth/argument-error', error.code); -} - - -function testValidate_optionalUndefined_multiple() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.string('password'), - fireauth.args.bool('emailVerified', true), - fireauth.args.bool('anonymous', true) - ]; - // Optional valid parameters explicitly passed. - var args = ['foo@bar.com', 'hunter2', false, true]; - fireauth.args.validate('myFunc', expectedArgs, args); - // Optional valid parameter not passed. - args = ['foo@bar.com', 'hunter2']; - fireauth.args.validate('myFunc', expectedArgs, args); - // Optional valid parameters passed as undefined. - args = ['foo@bar.com', 'hunter2', undefined, undefined]; - fireauth.args.validate('myFunc', expectedArgs, args); - // Optional valid parameter passed as undefined and then a valid parameter - // passed. - args = ['foo@bar.com', 'hunter2', undefined, false]; - fireauth.args.validate('myFunc', expectedArgs, args); - // Optional parameter passed as invalid. In this case null is invalid. - var error = assertThrows(function() { - args = ['foo@bar.com', 'hunter2', undefined, null]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals('auth/argument-error', error.code); -} - - -function testValidate_argumentsObj() { - function fooFunc() { - var expectedArgs = [ - fireauth.args.string('email'), - fireauth.args.string('password') - ]; - fireauth.args.validate('myFunc', expectedArgs, arguments); - } - fooFunc('me@site.com', 'myPassword'); -} - - -function testValidate_argumentsObj_invalid() { - var error = assertThrows(function() { - function fooFunc() { - fireauth.args.validate('fooFunc', [fireauth.args.string('name')], - arguments); - } - fooFunc(13); - }); - assertEquals('fooFunc failed: First argument "name" must be a valid ' + - 'string.', error.message); -} - - -function testValidate_multiFactorAssertion_valid() { - var expectedArgs = [fireauth.args.multiFactorAssertion()]; - var cred = fireauth.PhoneAuthProvider.credential('verificationId', 'code'); - var args = [fireauth.PhoneMultiFactorGenerator.assertion(cred)]; - assertNotThrows(function() { - fireauth.args.validate('myFunc', expectedArgs, args); - }); -} - - -function testValidate_multiFactorAssertion_invalid() { - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.multiFactorAssertion()]; - var args = ['foo']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals( - 'myFunc failed: First argument "multiFactorAssertion" must be a valid ' + - 'multiFactorAssertion.', - error.message); -} - - -function testValidate_multiFactorAssertion_null() { - var error = assertThrows(function() { - var expectedArgs = [fireauth.args.multiFactorAssertion()]; - var args = [null]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals( - 'myFunc failed: First argument "multiFactorAssertion" must be a valid ' + - 'multiFactorAssertion.', - error.message); -} - - -function testValidate_multiFactorAssertion_withType_valid() { - var expectedArgs = [fireauth.args.multiFactorAssertion('phone')]; - var cred = fireauth.PhoneAuthProvider.credential('verificationId', 'code'); - var args = [fireauth.PhoneMultiFactorGenerator.assertion(cred)]; - fireauth.args.validate('myFunc', expectedArgs, args); -} - - -function testValidate_multiFactorAssertion_withType_invalid() { - var expectedMessage = - 'myFunc failed: First argument "otherMultiFactorAssertion" ' + - 'must be a valid other multiFactorAssertion.'; - var expectedArgs = [fireauth.args.multiFactorAssertion('other')]; - var error = assertThrows(function() { - var cred = fireauth.PhoneAuthProvider.credential('verificationId', 'code'); - var args = [fireauth.PhoneMultiFactorGenerator.assertion(cred)]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals(expectedMessage, error.message); - - error = assertThrows(function() { - var args = ['I am the wrong type']; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals(expectedMessage, error.message); -} - - -function testValidate_multiFactorAssertion_withTypeAndName_invalid() { - var expectedMessage = 'myFunc failed: First argument "myArgName" ' + - 'must be a valid other multiFactorAssertion.'; - var expectedArgs = - [fireauth.args.multiFactorAssertion('other', 'myArgName')]; - var error = assertThrows(function() { - var cred = fireauth.PhoneAuthProvider.credential('verificationId', 'code'); - var args = [fireauth.PhoneMultiFactorGenerator.assertion(cred)]; - fireauth.args.validate('myFunc', expectedArgs, args); - }); - assertEquals(expectedMessage, error.message); -} diff --git a/packages/auth/test/auth_test.js b/packages/auth/test/auth_test.js deleted file mode 100644 index ab32b990184..00000000000 --- a/packages/auth/test/auth_test.js +++ /dev/null @@ -1,11771 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for auth.js - */ - -goog.provide('fireauth.AuthTest'); - -goog.require('fireauth.ActionCodeInfo'); -goog.require('fireauth.ActionCodeSettings'); -goog.require('fireauth.Auth'); -goog.require('fireauth.AuthCredential'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthErrorWithCredential'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.AuthEventManager'); -goog.require('fireauth.AuthSettings'); -goog.require('fireauth.AuthUser'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.MultiFactorAssertion'); -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.SAMLAuthProvider'); -goog.require('fireauth.StsTokenManager'); -goog.require('fireauth.UserEventType'); -goog.require('fireauth.authStorage'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('fireauth.deprecation'); -/** @suppress {extraRequire} Needed for firebase.app().auth() */ -goog.require('fireauth.exports'); -goog.require('fireauth.idp'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.object'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.PendingRedirectManager'); -goog.require('fireauth.storage.RedirectUserManager'); -goog.require('fireauth.storage.UserManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Timer'); -goog.require('goog.Uri'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.testing.AsyncTestCase'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.events'); -goog.require('goog.testing.events.Event'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.AuthTest'); - - -var appId1 = 'appId1'; -var appId2 = 'appId2'; -var auth1 = null; -var auth2 = null; -var authInternal1 = null; -var authInternal2 = null; -var app1 = null; -var app2 = null; -var authUi1 = null; -var authUi2 = null; -var config1 = null; -var config2 = null; -var config3 = null; -var rpcHandler = null; -var token = null; -var accountInfo = { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'emailVerified': true -}; -var accountInfoWithTenantId = { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'emailVerified': true, - 'tenantId': '123456789012' -}; -// accountInfo in the format of a getAccountInfo response. -var getAccountInfoResponse = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] -}; -// A sample JWT, along with its decoded contents. -var idTokenGmail = { - data: { - sub: '679', - aud: '204241631686', - provider_id: 'gmail.com', - email: 'test123456@gmail.com', - federated_id: 'https://www.google.com/accounts/123456789' - } -}; -var expectedTokenResponse; -var expectedTokenResponse2; -var expectedTokenResponse3; -var expectedTokenResponse4; -var expectedTokenResponseWithIdPData; -var expectedAdditionalUserInfo; -var expectedGoogleCredential; -var expectedSamlTokenResponseWithIdPData; -var expectedSamlAdditionalUserInfo; -var now = Date.now(); - -var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); -var mockControl; -var ignoreArgument; - -var stubs = new goog.testing.PropertyReplacer(); -var angular = {}; -var currentUserStorageManager; -var redirectUserStorageManager; -var timeoutDelay = 30000; -var clock; - -var actionCodeSettings = { - 'url': 'https://www.example.com/?state=abc', - 'iOS': { - 'bundleId': 'com.example.ios' - }, - 'android': { - 'packageName': 'com.example.android', - 'installApp': true, - 'minimumVersion': '12' - }, - 'handleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' -}; -var mockLocalStorage; -var mockSessionStorage; -var jwt1; -var jwt2; -var jwt3; -var multiFactorErrorServerResponse; -var multiFactorTokenResponse; -var multiFactorGetAccountInfoResponse; - - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - // Disable Auth event manager for testing unless needed. - fireauth.AuthEventManager.ENABLED = false; - // Assume origin is a valid one. - simulateWhitelistedOrigin(); - // Simulate tab can run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - // In case the tests are run from an iframe. - stubs.replace( - fireauth.util, - 'isIframe', - function() { - return false; - }); - // Called on init state when a user is logged in. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - stubs.replace( - fireauth.util, - 'getCurrentUrl', - function() {return 'http://localhost';}); - initializeMockStorage(); - jwt1 = fireauth.common.testHelper.createMockJwt( - {'group': '1'}, now + 3600 * 1000); - jwt2 = fireauth.common.testHelper.createMockJwt( - {'group': '2'}, now + 3600 * 1000); - jwt3 = fireauth.common.testHelper.createMockJwt( - {'group': '3'}, now + 3600 * 1000); - idTokenGmail.data.exp = now / 1000 + 3600; - idTokenGmail.jwt = - fireauth.common.testHelper.createMockJwt(idTokenGmail.data); - // Initialize App and Auth instances. - config1 = { - apiKey: 'apiKey1' - }; - config2 = { - apiKey: 'apiKey2' - }; - config3 = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // Same as config3 but with a different authDomain. - config4 = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain2.firebaseapp.com', - 'appName': 'appId1' - }; - expectedTokenResponse = { - 'idToken': jwt1, - 'refreshToken': 'REFRESH_TOKEN', - 'expiresIn': '3600' - }; - expectedTokenResponse2 = { - 'idToken': jwt2, - 'refreshToken': 'REFRESH_TOKEN2', - 'expiresIn': '3600' - }; - expectedTokenResponse3 = { - 'idToken': jwt3, - 'refreshToken': 'REFRESH_TOKEN3', - 'expiresIn': '3600' - }; - expectedTokenResponse4 = { - // Sample ID token with provider password and email user@example.com. - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'iss': 'https://securetoken.google.com/12345678', - 'picture': 'https://plus.google.com/abcdefghijklmnopqrstu', - 'aud': '12345678', - 'auth_time': 1510357622, - 'sub': 'abcdefghijklmnopqrstu', - 'iat': 1510357622, - 'exp': now / 1000 + 3600, - 'email': 'user@example.com', - 'email_verified': true, - 'firebase': { - 'identities': { - 'email': [ - 'user@example.com' - ] - }, - 'sign_in_provider': 'password' - } - }), - 'refreshToken': 'REFRESH_TOKEN4', - 'expiresIn': '3600' - }; - expectedTokenResponseWithIdPData = { - 'idToken': jwt1, - 'refreshToken': 'REFRESH_TOKEN', - 'expiresIn': '3600', - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + - 'me":{"givenName":"John","familyName":"Doe"}}' - }; - expectedAdditionalUserInfo = { - 'profile': { - 'kind': 'plus#person', - 'displayName': 'John Doe', - 'name': { - 'givenName': 'John', - 'familyName': 'Doe' - } - }, - 'providerId': 'google.com', - 'isNewUser': false - }; - expectedGoogleCredential = fireauth.GoogleAuthProvider.credential( - 'googleIdToken', 'googleAccessToken'); - expectedSamlTokenResponseWithIdPData = { - 'idToken': jwt1, - 'refreshToken': 'REFRESH_TOKEN', - 'expiresIn': '3600', - 'providerId': 'saml.provider', - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + - 'me":{"givenName":"John","familyName":"Doe"}}' - }; - expectedSamlAdditionalUserInfo = { - 'profile': { - 'kind': 'plus#person', - 'displayName': 'John Doe', - 'name': { - 'givenName': 'John', - 'familyName': 'Doe' - } - }, - 'providerId': 'saml.provider', - 'isNewUser': false - }; - multiFactorTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'sub': 'defaultUserId', - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }), - 'refreshToken': 'MULTI_FACTOR_REFRESH_TOKEN' - }; - multiFactorErrorServerResponse = { - 'mfaInfo': [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+*******1234' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+*******6789' - } - ], - 'mfaPendingCredential': 'PENDING_CREDENTIAL', - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + - '"name":{"givenName":"John","familyName":"Doe"}}' - }; - multiFactorGetAccountInfoResponse = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': 'user@default.com', - 'emailVerified': true, - 'displayName': 'defaultDisplayName', - 'providerUserInfo': [], - 'photoUrl': 'https://www.default.com/default/default.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'lastLoginAt': '1506050282000', - 'createdAt': '1506050282000', - 'mfaInfo': [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+16505551234' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+16505556789' - } - ] - }] - }; - rpcHandler = new fireauth.RpcHandler('apiKey1'); - token = new fireauth.StsTokenManager(rpcHandler); - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt1); - token.setExpiresIn(3600); - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); -} - - -function tearDown() { - // Delete all Firebase apps created - var promises = []; - for (var i = 0; i < firebase.apps.length; i++) { - promises.push(firebase.apps[i].delete()); - } - if (promises.length) { - // Wait for all Firebase apps to be deleted. - asyncTestCase.waitForSignals(1); - goog.Promise.all(promises).then(function() { - // Dispose clock then. Disposing before will throw an error in IE 11. - goog.dispose(clock); - asyncTestCase.signal(); - }); - if (clock) { - // Some IE browsers like IE 11, native promise hangs if this is not called - // when clock is mocked. - // app.delete() will hang (it uses the native Promise). - clock.tick(); - } - } else if (clock) { - // No Firebase apps created, dispose clock immediately. - goog.dispose(clock); - } - - fireauth.AuthEventManager.manager_ = {}; - window.localStorage.clear(); - window.sessionStorage.clear(); - rpcHandler = null; - token = null; - if (auth1) { - auth1.delete(); - } - auth1 = null; - if (auth2) { - auth2.delete(); - } - auth2 = null; - app1 = null; - app2 = null; - config1 = null; - config2 = null; - config3 = null; - multiFactorErrorServerResponse = null; - multiFactorTokenResponse = null; - multiFactorGetAccountInfoResponse = null; - stubs.reset(); - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } - fireauth.authStorage.Manager.clear(); - currentUserStorageManager = null; - redirectUserStorageManager = null; - if (goog.global.document) { - fireauth.util.onDomReady().then(function () { - var el = goog.global.document.querySelector('.firebase-emulator-warning'); - if (el) { - el.parentNode.removeChild(el); - } - }); - } -} - - -function testInitializeApp_noApiKey() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_API_KEY); - // Initialize app without an API key. - var appWithoutApiKey = firebase.initializeApp({}, 'appWithoutApiKey'); - // Initialization without API key should not throw an Auth error. - // Only on Auth explicit initialization will the error be visibly thrown. - try { - appWithoutApiKey.auth(); - fail('Auth initialization should fail due to missing api key.'); - } catch (e) { - fireauth.common.testHelper.assertErrorEquals(expectedError, e); - } -} - - -/** - * Assert the Auth token listener is called once. - * @param {!fireauth.Auth} auth The Auth instance. - */ -function assertAuthTokenListenerCalledOnce(auth) { - var calls = 0; - asyncTestCase.waitForSignals(1); - auth.addAuthTokenListener(function(token) { - // Should only trigger once after init state. - calls++; - assertEquals(1, calls); - asyncTestCase.signal(); - }); -} - - -/** Initializes mock storages. */ -function initializeMockStorage() { - // Simulate tab can run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); -} - - -/** - * Simulates current origin is whitelisted for popups and redirects. - */ -function simulateWhitelistedOrigin() { - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); -} - - -/** - * Asserts that two users are equivalent. Plain assertObjectEquals may not work - * as the expiration time may sometimes be off by a second. This takes that into - * account. - * @param {!fireauth.AuthUser} expected - * @param {!fireauth.AuthUser} actual - */ -function assertUserEquals(expected, actual) { - fireauth.common.testHelper.assertUserEqualsInWithDiffApikey(expected, actual); -} - - -function testAuth_noApiKey() { - try { - app1 = firebase.initializeApp({}, appId1); - app1.auth(); - fail('Should have thrown an error!'); - } catch (e) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_API_KEY), e); - } -} - - -function testToJson_noUser() { - // Test toJSON with no user signed in. - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - var authPlainObject = { - 'apiKey': config1['apiKey'], - 'authDomain': config1['authDomain'], - 'appName': appId1, - 'currentUser': null - }; - assertObjectEquals(authPlainObject, auth1.toJSON()); - // Make sure JSON.stringify works and uses underlying toJSON. - assertEquals(JSON.stringify(auth1), JSON.stringify(auth1.toJSON())); -} - - -function testToJson_withUser() { - // Test toJSON with a user signed in. - stubs.reset(); - fireauth.AuthEventManager.ENABLED = false; - initializeMockStorage(); - // Simulate available token. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Reload will be called on init. - return goog.Promise.resolve(); - }); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var storageKey = fireauth.util.createStorageKey(config1['apiKey'], appId1); - var authPlainObject = { - 'apiKey': config1['apiKey'], - 'authDomain': config1['authDomain'], - 'appName': appId1, - 'currentUser': user1.toJSON() - }; - currentUserStorageManager = new fireauth.storage.UserManager(storageKey); - // Simulate logged in user, save to storage, it will be picked up on init - // Auth state. - currentUserStorageManager.setCurrentUser(user1).then(function() { - auth1 = app1.auth(); - auth1.onIdTokenChanged(function(user) { - assertObjectEquals(authPlainObject, auth1.toJSON()); - // Make sure JSON.stringify works and uses underlying toJSON. - assertEquals(JSON.stringify(auth1), JSON.stringify(auth1.toJSON())); - asyncTestCase.signal(); - }); - }); -} - - -function testGetStorageKey() { - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertEquals(config1['apiKey'] + ':' + appId1, auth1.getStorageKey()); -} - - -function testAuth_initListeners_disabled() { - // Test with init listener disabled. - app1 = firebase.initializeApp(config1, appId1); - app2 = firebase.initializeApp(config2, appId2); - auth1 = app1.auth(); - auth2 = app2.auth(); - assertObjectEquals(app1, auth1.app_()); - assertObjectEquals(app2, auth2.app_()); -} - - -function testApp() { - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertObjectEquals(app1, auth1.app_()); -} - - -function testGetRpcHandler() { - app1 = firebase.initializeApp(config1, appId1); - app2 = firebase.initializeApp(config2, appId2); - auth1 = app1.auth(); - auth2 = app2.auth(); - assertNotNull(auth1.getRpcHandler()); - assertNotNull(auth2.getRpcHandler()); - assertEquals('apiKey1', auth1.getRpcHandler().getApiKey()); - assertEquals('apiKey2', auth2.getRpcHandler().getApiKey()); -} - - -function testAuth_rpcHandlerEndpoints() { - // Confirm expected endpoint config passed to underlying RPC handler. - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - stubs.replace( - fireauth.constants, - 'getEndpointConfig', - function(opt_id) { - return endpointConfig; - }); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor(config1['apiKey'], endpointConfig, ignoreArgument) - .$returns(rpcHandler); - mockControl.$replayAll(); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); -} - - -function testAuth_rpcHandlerEndpoints_tenantId() { - // Confirm expected endpoint config passed to underlying RPC handler. - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - stubs.replace( - fireauth.constants, - 'getEndpointConfig', - function(opt_id) { - return endpointConfig; - }); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor(config1['apiKey'], endpointConfig, ignoreArgument) - .$returns(rpcHandler); - // Tenant ID of RPC handler should be updated. - rpcHandler.updateTenantId('TENANT_ID').$once(); - mockControl.$replayAll(); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - // Sets the tenant ID on Auth instance. - auth1.tenantId = 'TENANT_ID'; -} - - -function testCurrentUser() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var user = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - auth1.setCurrentUser_(user); - assertUserEquals(user, auth1['currentUser']); -} - - -function testAuthSettings() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - - assertTrue(auth1['settings'] instanceof fireauth.AuthSettings); - assertFalse(auth1['settings']['appVerificationDisabledForTesting']); - auth1['settings']['appVerificationDisabledForTesting'] = true; - assertTrue(auth1['settings']['appVerificationDisabledForTesting']); - // Confirm validation is applied. - var error = assertThrows(function() { - auth1['settings']['appVerificationDisabledForTesting'] = 'invalid'; - }); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'appVerificationDisabledForTesting failed: ' + - '"appVerificationDisabledForTesting" must be a boolean.'); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - - // Confirm settings is a read-only property. - auth1['settings'] = null; - assertTrue(auth1['settings'] instanceof fireauth.AuthSettings); -} - - -function testLogFramework() { - // Helper function to get the client version for the test. - var getVersion = function(frameworks) { - return fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION, - frameworks); - }; - // Pipe through all framework IDs. - stubs.replace( - fireauth.util, - 'getFrameworkIds', - function(providedFrameworks) { - return providedFrameworks; - }); - // Listen to all client version update calls on RpcHandler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateClientVersion', - goog.testing.recordFunction()); - var handler = goog.testing.recordFunction(); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - - // Listen to all frameworkChanged events dispatched by the Auth instance. - goog.events.listen( - auth1, - fireauth.constants.AuthEventType.FRAMEWORK_CHANGED, - handler); - assertArrayEquals([], auth1.getFramework()); - assertEquals(0, handler.getCallCount()); - assertEquals( - 0, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); - - // Add version and confirm event triggered and client version updated in - // RpcHandler. - auth1.logFramework('angularfire'); - assertArrayEquals(['angularfire'], auth1.getFramework()); - assertEquals(1, handler.getCallCount()); - assertArrayEquals( - ['angularfire'], handler.getLastCall().getArgument(0).frameworks); - assertEquals( - 1, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); - assertEquals( - getVersion(['angularfire']), - fireauth.RpcHandler.prototype.updateClientVersion.getLastCall() - .getArgument(0)); - - // Add another version and confirm event triggered and client version updated - // in RpcHandler. - auth1.logFramework('firebaseui'); - assertArrayEquals(['angularfire', 'firebaseui'], auth1.getFramework()); - assertEquals(2, handler.getCallCount()); - assertArrayEquals( - ['angularfire', 'firebaseui'], - handler.getLastCall().getArgument(0).frameworks); - assertEquals( - 2, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); - assertEquals( - getVersion(['angularfire', 'firebaseui']), - fireauth.RpcHandler.prototype.updateClientVersion.getLastCall() - .getArgument(0)); -} - - -function testInternalLogFramework() { - // Record all calls to logFramework. - stubs.replace( - fireauth.Auth.prototype, - 'logFramework', - goog.testing.recordFunction()); - // Confirm INTERNAL.logFramework calls logFramework. - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertEquals(0, fireauth.Auth.prototype.logFramework.getCallCount()); - auth1.INTERNAL.logFramework('firebaseui'); - assertEquals(1, fireauth.Auth.prototype.logFramework.getCallCount()); - assertEquals( - 'firebaseui', - fireauth.Auth.prototype.logFramework.getLastCall().getArgument(0)); -} - - -function testUseDeviceLanguage() { - // Listen to all custom locale header calls on RpcHandler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateCustomLocaleHeader', - goog.testing.recordFunction()); - var handler = goog.testing.recordFunction(); - stubs.replace(fireauth.util, 'getUserLanguage', function() { - return 'de'; - }); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - // Listen to all languageCodeChanged events dispatched by the Auth instance. - goog.events.listen( - auth1, - fireauth.constants.AuthEventType.LANGUAGE_CODE_CHANGED, - handler); - assertNull(auth1.languageCode); - assertEquals(0, handler.getCallCount()); - assertEquals( - 0, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - - // Update to English and confirm event triggered and custom locale updated in - // RpcHandler. - auth1.languageCode = 'en'; - assertEquals('en', auth1.languageCode); - assertEquals(1, handler.getCallCount()); - assertEquals('en', handler.getLastCall().getArgument(0).languageCode); - assertEquals( - 1, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertEquals( - 'en', - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); - - // Update to device language and confirm event triggered and custom locale - // updated in RpcHandler. - auth1.useDeviceLanguage(); - assertEquals('de', auth1.languageCode); - assertEquals(2, handler.getCallCount()); - assertEquals('de', handler.getLastCall().getArgument(0).languageCode); - assertEquals( - 2, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertEquals( - 'de', - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); - - // Developer should still be able to set the language code. - // Update to French and confirm event triggered and custom locale updated in - // RpcHandler. - auth1.languageCode = 'fr'; - assertEquals('fr', auth1.languageCode); - assertEquals(3, handler.getCallCount()); - assertEquals('fr', handler.getLastCall().getArgument(0).languageCode); - assertEquals( - 3, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertEquals( - 'fr', - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); - - // Switch back to device language. - auth1.useDeviceLanguage(); - assertEquals('de', auth1.languageCode); - assertEquals(4, handler.getCallCount()); - assertEquals('de', handler.getLastCall().getArgument(0).languageCode); - assertEquals( - 4, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertEquals( - 'de', - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); - - // Changing to the same language should not trigger any change. - auth1.languageCode = 'de'; - assertEquals(4, handler.getCallCount()); - assertEquals( - 4, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - - // Update to null and confirm event triggered and custom locale updated in - // RpcHandler. - auth1.languageCode = null; - assertNull(auth1.languageCode); - assertEquals(5, handler.getCallCount()); - assertNull(handler.getLastCall().getArgument(0).languageCode); - assertEquals( - 5, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertNull( - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); -} - - -function testUseEmulator() { - // Listen to emulator config calls on RpcHandler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmulatorConfig', - goog.testing.recordFunction()); - stubs.replace( - fireauth.util, - 'consoleInfo', - goog.testing.recordFunction()); - var handler = goog.testing.recordFunction(); - stubs.replace( - fireauth.AuthSettings.prototype, - 'setAppVerificationDisabledForTesting', - goog.testing.recordFunction()); - - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - - // Listen to all emulatorConfigChange events dispatched by the Auth instance. - goog.events.listen( - auth1, - fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED, - handler); - - assertUndefined(fireauth.constants.emulatorConfig); - assertEquals(0, handler.getCallCount()); - assertEquals( - 0, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); - assertEquals(0, fireauth.util.consoleInfo.getCallCount()); - assertEquals( - 0, - fireauth.AuthSettings.prototype.setAppVerificationDisabledForTesting. - getCallCount()); - - // Update the emulator config. - auth1.useEmulator('http://emulator.test.domain:1234'); - assertObjectEquals( - { - protocol: 'http', - host: 'emulator.test.domain', - port: 1234, - options: {disableWarnings: false}, - }, - auth1.emulatorConfig); - // Should notify the RPC handler. - assertEquals( - 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); - assertObjectEquals( - { - url: 'http://emulator.test.domain:1234', - disableWarnings: false, - }, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0) - ); - // Should emit a console warning and a banner. - assertEquals(1, fireauth.util.consoleInfo.getCallCount()); - if (goog.global.document) { - asyncTestCase.waitForSignals(1); - fireauth.util.onDomReady().then(() => { - const el = - goog.global.document.querySelector('.firebase-emulator-warning'); - assertNotNull(el); - asyncTestCase.signal(); - }); - } - // Should disable App verification. - assertEquals( - true, - fireauth.AuthSettings.prototype.setAppVerificationDisabledForTesting. - getLastCall().getArgument(0)); - - // Update to the same config should not trigger event again. - auth1.useEmulator('http://emulator.test.domain:1234'); - assertObjectEquals( - { - protocol: 'http', - host: 'emulator.test.domain', - port: 1234, - options: {disableWarnings: false}, - }, - auth1.emulatorConfig); - assertEquals( - 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); - assertEquals(1, fireauth.util.consoleInfo.getCallCount()); - - // Updating to different config should still not trigger event. - auth1.useEmulator('http://emulator.other.domain:9876'); - assertObjectEquals( - { - protocol: 'http', - host: 'emulator.test.domain', - port: 1234, - options: {disableWarnings: false}, - }, - auth1.emulatorConfig); - assertEquals( - 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); -} - - -function testUseEmulator_withDisableWarnings() { - // Listen to emulator config calls on RpcHandler. - stubs.replace( - fireauth.RpcHandler.prototype, 'updateEmulatorConfig', - goog.testing.recordFunction()); - stubs.replace(fireauth.util, 'consoleInfo', goog.testing.recordFunction()); - const handler = goog.testing.recordFunction(); - - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - - // Listen to all emulatorConfigChange events dispatched by the Auth instance. - goog.events.listen( - auth1, fireauth.constants.AuthEventType.EMULATOR_CONFIG_CHANGED, handler); - - assertUndefined(fireauth.constants.emulatorConfig); - assertEquals(0, handler.getCallCount()); - assertEquals( - 0, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); - assertEquals(0, fireauth.util.consoleInfo.getCallCount()); - - // Update the emulator config. - auth1.useEmulator( - 'http://emulator.test.domain:1234', {disableWarnings: true}); - assertObjectEquals( - { - protocol: 'http', - host: 'emulator.test.domain', - port: 1234, - options: {disableWarnings: true}, - }, - auth1.emulatorConfig); - // Should notify the RPC handler. - assertEquals( - 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); - assertObjectEquals( - { - url: 'http://emulator.test.domain:1234', - disableWarnings: true, - }, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0)); - // Should emit a console info but not a banner. - assertEquals(1, fireauth.util.consoleInfo.getCallCount()); - if (goog.global.document) { - asyncTestCase.waitForSignals(1); - fireauth.util.onDomReady().then(() => { - const el = - goog.global.document.querySelector('.firebase-emulator-warning'); - assertNull(el); - asyncTestCase.signal(); - }); - } -} - - -function testEmulatorConfig() { - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - - // Update the emulator config. - auth1.useEmulator( - 'http://emulator.test.domain:1234', {disableWarnings: true}); - assertObjectEquals( - { - protocol: 'http', - host: 'emulator.test.domain', - port: 1234, - options: {disableWarnings: true}, - }, - auth1.emulatorConfig); -} - - -/** - * Asserts that the port is correctly set to null if no port supplied. - */ -function testEmulatorConfig_noPortSpecified() { - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - - // Update the emulator config. - auth1.useEmulator('http://emulator.test.domain'); - assertObjectEquals( - { - protocol: 'http', - host: 'emulator.test.domain', - port: null, - options: {disableWarnings: false}, - }, - auth1.emulatorConfig); -} - - -/** - * Asserts that the port is correctly assigned 0 if specifically set to 0 for - * some reason. Also checks https protocol. - */ -function testEmulatorConfig_portZeroAndHttpsSpecified() { - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - - // Update the emulator config. - auth1.useEmulator('https://emulator.test.domain:0'); - assertObjectEquals( - { - protocol: 'https', - host: 'emulator.test.domain', - port: 0, - options: {disableWarnings: false}, - }, - auth1.emulatorConfig); -} - - -/** - * Asserts that the function returns null if useEmulator is not called. - */ -function testEmulatorConfig_nullIfNoEmulatorConfig() { - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - - assertNull(auth1.emulatorConfig); -} - - -function testGetSetTenantId() { - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - // Tenant ID should be initialized to null. - assertNull(auth1.tenantId); - assertNull(auth1.getRpcHandler().getTenantId()); - // Updating tenant ID on Auth should also update the tenant ID of RPC handler. - auth1.tenantId = 'TENANT_ID1'; - assertEquals('TENANT_ID1', auth1.tenantId); - assertEquals('TENANT_ID1', auth1.getRpcHandler().getTenantId()); - // Reset tenant ID to null. - auth1.tenantId = null; - assertNull(auth1.tenantId); - assertNull(auth1.getRpcHandler().getTenantId()); - - // Test getter and setter. - auth1.setTenantId('TENANT_ID2'); - assertEquals('TENANT_ID2', auth1.getTenantId()); - assertEquals('TENANT_ID2', auth1.tenantId); - auth1.tenantId = null; - assertNull(auth1.getTenantId()); - assertNull(auth1.tenantId); -} - - -/** - * Test Auth state listeners triggered on listener add even when initial state - * is null. However it will only first trigger when state is resolved. - */ -function testAddAuthTokenListener_initialNullState() { - var user = new fireauth.AuthUser(config1, expectedTokenResponse, accountInfo); - stubs.reset(); - // Simulate no state returned. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - return goog.Promise.resolve(null); - }); - initializeMockStorage(); - // Suppress addStateChangeListener. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) {}); - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Generate new token on next call to trigger listeners. - return goog.Promise.resolve({ - accessToken: jwt2, - refreshToken: 'refreshToken' - }); - }); - var listener1 = mockControl.createFunctionMock('listener1'); - var listener2 = mockControl.createFunctionMock('listener2'); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - listener1(null).$does(function() { - // Should be triggered after state is resolved. - assertEquals(0, marker); - // Increment marker. - marker++; - auth1.addAuthTokenListener(listener2); - }); - listener2(null).$does(function() { - // Should be triggered after listener2 is added. - assertEquals(1, marker); - // Increment marker. - marker++; - // Auth state change notification should also trigger immediately now. - // Simulate Auth event to trigger both listeners. - auth1.setCurrentUser_(user); - user.getIdToken(); - }); - listener1(jwt2).$does(function() { - // Marker should confirm listener triggered after notifyAuthListeners_. - assertEquals(2, marker); - asyncTestCase.signal(); - }); - listener2(jwt2).$does(function() { - // Marker should confirm listener triggered after notifyAuthListeners_. - assertEquals(2, marker); - asyncTestCase.signal(); - }); - mockControl.$replayAll(); - // Wait for last 2 expected listener calls. - asyncTestCase.waitForSignals(2); - // Keep track of what is triggering the events. - var marker = 0; - // Test listeners called when state first determined. - auth1.addAuthTokenListener(listener1); -} - - -/** - * Test Auth state listeners triggered on listener add even when initial state - * is not null (signed in user). However it will only first trigger when state - * is resolved. - */ -function testAddAuthTokenListener_initialValidState() { - var user = new fireauth.AuthUser(config1, expectedTokenResponse, accountInfo); - stubs.reset(); - // Simulate valid state returned. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - return goog.Promise.resolve(user); - }); - initializeMockStorage(); - // Suppress addStateChangeListener. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) {}); - // Simulate available token. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Internally calls Auth user listeners. - return goog.Promise.resolve(); - }); - var currentAccessToken = jwt1; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Generate new token on next call to trigger listeners. - return goog.Promise.resolve({ - accessToken: currentAccessToken, - refreshToken: 'refreshToken' - }); - }); - // Keep track of what is triggering the events. - var marker = 0; - var listener1 = mockControl.createFunctionMock('listener1'); - var listener2 = mockControl.createFunctionMock('listener2'); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - listener1(jwt1).$does(function() { - // Should be triggered after state is resolved. - assertEquals(0, marker); - marker++; - // Now that state is determined, adding a new listener should resolve - // immediately. - auth1.addAuthTokenListener(listener2); - }); - listener2(jwt1).$does(function() { - // Should be triggered after listener2 is added. - assertEquals(1, marker); - // Increment marker. - marker++; - // Auth state change notification should also trigger immediately now. - // Simulate Auth event via getIdToken refresh to trigger both listeners. - currentAccessToken = 'newAccessToken'; - user.getIdToken(); - }); - listener1('newAccessToken').$does(function() { - // Marker should confirm listener triggered after notifyAuthListeners_. - assertEquals(2, marker); - asyncTestCase.signal(); - }); - listener2('newAccessToken').$does(function() { - // Marker should confirm listener triggered after notifyAuthListeners_. - assertEquals(2, marker); - asyncTestCase.signal(); - }); - mockControl.$replayAll(); - // Wait for last 2 expected listener calls. - asyncTestCase.waitForSignals(2); - // Test listeners called when state first determined. - auth1.addAuthTokenListener(listener1); -} - - -function testGetUid_userSignedIn() { - // Test getUid() on Auth instance and app instance with user previously - // signed in. - var accountInfo1 = {'uid': '1234'}; - asyncTestCase.waitForSignals(1); - // Get current user storage manager. - var storageKey = fireauth.util.createStorageKey(config1['apiKey'], appId1); - currentUserStorageManager = new fireauth.storage.UserManager(storageKey); - // Create test user instance. - var user = - new fireauth.AuthUser(config1, expectedTokenResponse, accountInfo1); - // Save test user. This will be loaded on Auth init. - currentUserStorageManager.setCurrentUser(user).then(function() { - // Initialize App and Auth. - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - authInternal1 = app1.container.getProvider('auth-internal').getImmediate(); - // Initially getUid() should return null; - assertNull(auth1.getUid()); - assertNull(authInternal1.getUid()); - // Listen to Auth changes. - var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { - // Unsubscribe of Auth state change listener. - unsubscribe(); - // Logged in test user should be detected. - // Confirm getUid() returns expected UID. - assertEquals(accountInfo1['uid'], auth1.getUid()); - assertEquals(accountInfo1['uid'], authInternal1.getUid()); - goog.Timer.promise(10).then(function() { - // Sign out. - return auth1.signOut(); - }).then(function() { - return goog.Timer.promise(10); - }).then(function() { - // getUid() should return null. - assertNull(auth1.getUid()); - assertNull(authInternal1.getUid()); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testGetUid_noUserSignedIn() { - // Test getUid() on Auth instance and App instance with no user previously - // signed in and new user signs in. - var accountInfo1 = {'uid': '1234'}; - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - return goog.Promise.resolve(user); - }); - // Simulate successful RpcHandler verifyPassword resolving with expected - // token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyPassword', function(email, password) { - // Return tokens for test user. - return goog.Promise.resolve(expectedTokenResponse); - }); - asyncTestCase.waitForSignals(1); - var user = - new fireauth.AuthUser(config1, expectedTokenResponse, accountInfo1); - // Initialize App and Auth. - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - authInternal1 = app1.container.getProvider('auth-internal').getImmediate(); - // Listen to Auth changes. - var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { - // Unsubscribe of Auth state change listener. - unsubscribe(); - // Initially getUid() should return null; - assertNull(auth1.getUid()); - assertNull(authInternal1.getUid()); - // Sign in with email and password. - auth1.signInWithEmailAndPassword('user@example.com', 'password') - .then(function(userCredential) { - // getUid() should return the test user UID. - assertEquals(accountInfo1['uid'], auth1.getUid()); - assertEquals(accountInfo1['uid'], authInternal1.getUid()); - asyncTestCase.signal(); - }); - }); -} - - -function testNotifyAuthListeners() { - // Simulate available token. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - return goog.Promise.resolve({ - accessToken: currentAccessToken, - refreshToken: 'refreshToken' - }); - }); - // User reloaded. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - var currentAccessToken = 'accessToken1'; - var app1AuthTokenListener = goog.testing.recordFunction(); - var app2AuthTokenListener = goog.testing.recordFunction(); - var user = new fireauth.AuthUser( - config1, expectedTokenResponse, accountInfo); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - - asyncTestCase.waitForSignals(2); - // Set current user on auth1. - currentUserStorageManager = new fireauth.storage.UserManager( - config1['apiKey'] + ':' + appId1); - currentUserStorageManager.setCurrentUser(user).then(function() { - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - authInternal1 = app1.container.getProvider('auth-internal').getImmediate(); - authInternal1.addAuthTokenListener(app1AuthTokenListener); - app2 = firebase.initializeApp(config2, appId2); - auth2 = app2.auth(); - authInternal2 = app2.container.getProvider('auth-internal').getImmediate(); - authInternal2.addAuthTokenListener(app2AuthTokenListener); - // Confirm all listeners reset. - assertEquals(0, listener1.getCallCount()); - assertEquals(0, listener2.getCallCount()); - assertEquals(0, listener3.getCallCount()); - assertEquals(0, app1AuthTokenListener.getCallCount()); - assertEquals(0, app2AuthTokenListener.getCallCount()); - auth1.addAuthTokenListener(listener1); - auth1.addAuthTokenListener(listener2); - auth2.addAuthTokenListener(listener3); - // Wait for state to be ready on auth1. - var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { - unsubscribe(); - // Listener 1 and 2 triggered. - assertEquals(1, listener1.getCallCount()); - assertEquals(listener1.getCallCount(), listener2.getCallCount()); - // First trigger on init state. - assertEquals( - listener1.getCallCount(), - app1AuthTokenListener.getCallCount()); - assertEquals( - 'accessToken1', - app1AuthTokenListener.getLastCall().getArgument(0)); - // Remove first listener and reset. - auth1.removeAuthTokenListener(listener1); - listener1.reset(); - listener2.reset(); - app1AuthTokenListener.reset(); - // Force token change. - currentAccessToken = 'accessToken2'; - // Trigger getIdToken to force refresh and Auth token change. - auth1['currentUser'].getIdToken().then(function(token) { - assertEquals('accessToken2', token); - assertEquals(0, listener1.getCallCount()); - // Only listener2 triggered. - assertEquals(1, listener2.getCallCount()); - // Second trigger. - assertEquals( - 1, - app1AuthTokenListener.getCallCount()); - assertEquals( - 'accessToken2', - app1AuthTokenListener.getLastCall().getArgument(0)); - - // Remove remaining listeners and reset. - app1AuthTokenListener.reset(); - listener2.reset(); - auth1.removeAuthTokenListener(listener2); - authInternal1.removeAuthTokenListener(app1AuthTokenListener); - // Force token change. - currentAccessToken = 'accessToken3'; - auth1['currentUser'].getIdToken().then(function(token) { - assertEquals('accessToken3', token); - // No listeners triggered anymore since they are all unsubscribed. - assertEquals(0, app1AuthTokenListener.getCallCount()); - assertEquals(0, listener1.getCallCount()); - assertEquals(0, listener2.getCallCount()); - asyncTestCase.signal(); - }); - }); - }); - // Wait for state to be ready on auth2. - auth2.onIdTokenChanged(function(currentUser) { - // auth2 listener triggered on init with null state once. - assertEquals(1, listener3.getCallCount()); - assertEquals( - 1, - app2AuthTokenListener.getCallCount()); - assertNull( - app2AuthTokenListener.getLastCall().getArgument(0)); - assertNull(currentUser); - asyncTestCase.signal(); - }); - }); -} - - -/** - * Tests the notifications made to observers defined through the public API, - * when calling the notifyAuthListeners. - */ -function testNotifyAuthStateObservers() { - stubs.reset(); - // Simulate available token. - var counter = 0; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Generate new token on each call. - counter++; - return goog.Promise.resolve({ - accessToken: 'accessToken' + counter.toString(), - refreshToken: 'refreshToken' - }); - }); - // Simulate user logged in. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - return goog.Promise.resolve(user); - }); - initializeMockStorage(); - // Suppress addStateChangeListener. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) {}); - // Simulate available token. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - asyncTestCase.signal(); - // Token not refreshed, notifyAuthListeners_ should call regardless. - return goog.Promise.resolve(); - }); - var user = new fireauth.AuthUser(config3, expectedTokenResponse); - var observer1 = mockControl.createFunctionMock('observer1'); - var observer2 = mockControl.createFunctionMock('observer2'); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - observer1(user).$does(function(u) { - // Should be triggered after state is resolved. - assertEquals(0, marker++); - auth1.onIdTokenChanged(observer2); - }); - observer2(user).$does(function(u) { - // Should be triggered after listener2 is added. - assertEquals(1, marker++); - // Auth state change notification should also trigger immediately now. - // Simulate Auth event to trigger both listeners. - user.getIdToken(); - }); - observer1(user).$does(function(u) { - // Should be triggered after state is resolved. - assertEquals(2, marker++); - }); - observer2(user).$does(function(u) { - // Should be triggered after listener2 is added. - assertEquals(3, marker++); - // Auth state change notification should also trigger immediately now. - // Simulate Auth event to trigger listeners. - user.getIdToken(); - // Removes the first observer. - unsubscribe1(); - }); - observer2(user).$does(function(u) { - // Marker should confirm listener triggered after notifyAuthListeners_. - assertEquals(4, marker++); - asyncTestCase.signal(); - }); - mockControl.$replayAll(); - // Wait for the final observer call and the 2 intermediate internal callbacks. - asyncTestCase.waitForSignals(2); - // Keep track of what is triggering the events. - var marker = 0; - // Test listeners called when state first determined. - var unsubscribe1 = auth1.onIdTokenChanged(observer1); -} - - -/** - * Tests the notifications made to user state observers defined through the - * public API, when calling the notifyAuthListeners. - */ -function testAuth_onAuthStateChanged() { - stubs.reset(); - // Simulate available token. - var counter = 0; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Generate new token on each call. - counter++; - return goog.Promise.resolve({ - accessToken: 'accessToken' + counter.toString(), - refreshToken: 'refreshToken' - }); - }); - var expectedTokenResponse2 = { - 'idToken': jwt2, - 'refreshToken': 'REFRESH_TOKEN2' - }; - // Simulate user initially logged in. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - return goog.Promise.resolve(user); - }); - initializeMockStorage(); - // Suppress addStateChangeListener. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) {}); - // Simulate available token. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Token not refreshed, notifyAuthListeners_ should call regardless. - return goog.Promise.resolve(); - }); - // Simulate new user sign in. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function() { - return goog.Promise.resolve(user2); - }); - var user = new fireauth.AuthUser( - config3, expectedTokenResponse, {'uid': '1234'}); - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse2, {'uid': '5678'}); - var observer1 = mockControl.createFunctionMock('observer1'); - var observer2 = mockControl.createFunctionMock('observer2'); - var observer3 = mockControl.createFunctionMock('observer3'); - var unsubscribe1, unsubscribe2, unsubscribe3; - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - observer1(user).$does(function(u) { - // Should be triggered after state is resolved. - assertEquals(0, marker++); - // This should not trigger the listeners. - user.getIdToken().then(function(token) { - assertEquals(1, marker++); - // Add new observer. - unsubscribe2 = auth1.onAuthStateChanged(observer2); - }); - }).$once(); - observer2(user).$does(function(u) { - // Should be triggered after observer2 is added. - assertEquals(2, marker++); - // This should not trigger the listeners. - user.getIdToken().then(function(token) { - assertEquals(3, marker++); - // Add new observer. - unsubscribe3 = auth1.onAuthStateChanged(observer3); - }); - }).$once(); - observer3(user).$does(function(u) { - // Should be triggered after observer3 is added. - assertEquals(4, marker++); - // Unsubscribe first observer. - unsubscribe1(); - // This should trigger the 2 other observers. - auth1.signOut(); - }).$once(); - observer2(null).$does(function(u) { - assertEquals(5, marker++); - }).$once(); - observer3(null).$does(function(u) { - assertEquals(6, marker++); - // Simulate new user sign in. Both observers should trigger with user2. - auth1.signInWithIdTokenResponse(expectedTokenResponse2); - }).$once(); - observer2(user2).$does(function(u) { - assertEquals(7, marker++); - }).$once(); - observer3(user2).$does(function(u) { - assertEquals(8, marker++); - // This should do nothing. - user2.getIdToken().then(function(token) { - assertEquals(9, marker++); - // Unsubscribe second observer. - unsubscribe2(); - // Sign out should trigger observer3. - auth1.signOut(); - }); - }).$once(); - observer3(null).$does(function(u) { - assertEquals(10, marker++); - // Unsubscribe observer3. - unsubscribe3(); - // Add observer1 again. - unsubscribe1 = auth1.onAuthStateChanged(observer1); - }).$once(); - observer1(null).$does(function(u) { - // Observer1 should trigger immediately. - assertEquals(11, marker++); - asyncTestCase.signal(); - }).$once(); - - mockControl.$replayAll(); - asyncTestCase.waitForSignals(1); - // Keep track of what is triggering the events. - var marker = 0; - // Test listeners called when state first determined. - unsubscribe1 = auth1.onAuthStateChanged(observer1); -} - - -function testFetchSignInMethodsForEmail() { - var email = 'foo@bar.com'; - var expectedSignInMethods = ['password', 'google.com']; - - asyncTestCase.waitForSignals(1); - - // Simulate successful RpcHandler fetchSignInMethodsForIdentifier. - stubs.replace( - fireauth.RpcHandler.prototype, 'fetchSignInMethodsForIdentifier', - function(data) { - assertObjectEquals(email, data); - return goog.Promise.resolve(expectedSignInMethods); - }); - - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - auth1.fetchSignInMethodsForEmail(email).then(function(signInMethods) { - assertArrayEquals(expectedSignInMethods, signInMethods); - asyncTestCase.signal(); - }); - assertAuthTokenListenerCalledOnce(auth1); -} - - -function testFetchSignInMethodsForEmail_error() { - var email = 'foo@bar.com'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - - asyncTestCase.waitForSignals(1); - - stubs.replace( - fireauth.RpcHandler.prototype, 'fetchSignInMethodsForIdentifier', - function(data) { - assertObjectEquals(email, data); - return goog.Promise.reject(expectedError); - }); - - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - auth1.fetchSignInMethodsForEmail(email) - .then(function(signInMethods) { - fail('fetchSignInMethodsForEmail should not resolve!'); - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - assertAuthTokenListenerCalledOnce(auth1); -} - - -function testIsSignInWithEmailLink() { - var emailLink1 = 'https://www.example.com/action?mode=signIn&' + - 'oobCode=oobCode&apiKey=API_KEY'; - var emailLink2 = 'https://www.example.com/action?mode=verifyEmail&' + - 'oobCode=oobCode&apiKey=API_KEY'; - var emailLink3 = 'https://www.example.com/action?mode=signIn'; - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - var isSignInLink1 = auth1.isSignInWithEmailLink(emailLink1); - assertEquals(true, isSignInLink1); - var isSignInLink2 = auth1.isSignInWithEmailLink(emailLink2); - assertEquals(false, isSignInLink2); - var isSignInLink3 = auth1.isSignInWithEmailLink(emailLink3); - assertEquals(false, isSignInLink3); -} - - -function testIsSignInWithEmailLink_deepLink() { - var deepLink1 = 'https://www.example.com/action?mode=signIn&oobCode=oobCode' + - '&apiKey=API_KEY'; - var deepLink2 = 'https://www.example.com/action?mode=verifyEmail&' + - 'oobCode=oobCode&apiKey=API_KEY'; - var deepLink3 = 'https://www.example.com/action?mode=signIn'; - - var emailLink1 = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink1); - var emailLink2= 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink2); - var emailLink3 = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink3); - var emailLink4 = 'comexampleiosurl://google/link?deep_link_id=' + - encodeURIComponent(deepLink1); - - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertEquals(true, auth1.isSignInWithEmailLink(emailLink1)); - assertEquals(false, auth1.isSignInWithEmailLink(emailLink2)); - assertEquals(false, auth1.isSignInWithEmailLink(emailLink3)); - assertEquals(true, auth1.isSignInWithEmailLink(emailLink4)); -} - - -function testAuth_pendingPromises() { - asyncTestCase.waitForSignals(1); - // Simulate available token. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(appOptions, stsTokenResponse, opt_redirectStorageManager) { - // There should be pending promises before the sign in call below. - assertTrue(auth1.hasPendingPromises()); - return user1; - }); - // verifyCustomToken should be called with expected parameters and resolved - // with expected token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyCustomToken', - function(customToken) { - assertEquals('custom', customToken); - return goog.Promise.resolve(expectedTokenResponse); - }); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - auth1.signInWithCustomToken('custom').then(function(user) { - // No longer pending. - assertFalse(auth1.hasPendingPromises()); - asyncTestCase.signal(); - }); -} - - -function testAuth_delete() { - asyncTestCase.waitForSignals(2); - // Simulate available token. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - // Return a promise that does not fulfill to ensure that delete is - // called. - return new goog.Promise(function(resolve, reject) {}); - }); - // verifyCustomToken should be called with expected parameters and resolved - // with expected token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyCustomToken', - function(customToken) { - return goog.Promise.resolve(expectedTokenResponse); - }); - // Listener to removeStateChangeListener. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'removeCurrentUserChangeListener', - goog.testing.recordFunction()); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - auth1.signInWithCustomToken('customToken') - .then(function(user) { - fail('This promise should not fulfill after auth.delete!'); - }).thenCatch(function(error) { - // Cancellation error should trigger. - assertEquals(fireauth.authenum.Error.MODULE_DESTROYED, error.message); - asyncTestCase.signal(); - }); - auth1.onIdTokenChanged(function(user) { - auth1.INTERNAL.delete(); - assertFalse(auth1.hasPendingPromises()); - /** @suppress {missingRequire} */ - // confirm removeStateChangeListener called. - assertEquals( - 1, - fireauth.storage.UserManager.prototype.removeCurrentUserChangeListener - .getCallCount()); - // Try to change the language. - auth1.languageCode = 'fr'; - // No change should occur. - assertNull(auth1.languageCode); - asyncTestCase.signal(); - }); -} - - -/** - * Tests sendSignInLinkToEmail successful operation with action code settings. - */ -function testSendSignInLinkToEmail_success() { - var expectedEmail = 'user@example.com'; - // Simulate successful RpcHandler sendSignInLinkToEmail. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendSignInLinkToEmail', - function(email, actualActionCodeSettings) { - assertObjectEquals( - new fireauth.ActionCodeSettings(actionCodeSettings).buildRequest(), - actualActionCodeSettings); - assertEquals(expectedEmail, email); - return goog.Promise.resolve(expectedEmail); - }); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.sendSignInLinkToEmail(expectedEmail, actionCodeSettings) - .then(function() { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -/** - * Tests sendSignInLinkToEmail failing operation due to backend error. - */ -function testSendSignInLinkToEmail_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var expectedEmail = 'user@example.com'; - // Simulate unsuccessful RpcHandler sendSignInLinkToEmail. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendSignInLinkToEmail', - function(email, actualActionCodeSettings) { - assertObjectEquals( - new fireauth.ActionCodeSettings(actionCodeSettings).buildRequest(), - actualActionCodeSettings); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.sendSignInLinkToEmail(expectedEmail, actionCodeSettings) - .then(function() { - fail('sendSignInLinkToEmail should not resolve!'); - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests sendSignInLinkToEmail empty continue URL in action code settings. - */ -function testSendSignInLinkToEmail_emptyContinueUrl_error() { - var settings = { - 'url': '', - 'handleCodeInApp': true - }; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_CONTINUE_URI); - - var expectedEmail = 'user@example.com'; - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.sendSignInLinkToEmail(expectedEmail, settings) - .then(function() { - fail('sendSignInLinkToEmail should not resolve!'); - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests sendSignInLinkToEmail invalid handleCodeInApp settings. - */ -function testSendSignInLinkToEmail_handleCodeInApp_error() { - var settings = { - 'url': 'https://www.example.com/?state=abc', - 'handleCodeInApp': false - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'handleCodeInApp must be true when sending sign in link to email'); - var expectedEmail = 'user@example.com'; - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.sendSignInLinkToEmail(expectedEmail, settings) - .then(function() { - fail('sendSignInLinkToEmail should not resolve!'); - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests sendPasswordResetEmail successful operation with no action code - * settings. - */ -function testSendPasswordResetEmail_success() { - var expectedEmail = 'user@example.com'; - // Simulate successful RpcHandler sendPasswordResetEmail. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendPasswordResetEmail', - function(email, actualActionCodeSettings) { - assertObjectEquals({}, actualActionCodeSettings); - assertEquals(expectedEmail, email); - return goog.Promise.resolve(expectedEmail); - }); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.sendPasswordResetEmail(expectedEmail).then(function() { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -/** - * Tests sendPasswordResetEmail failing operation due to backend error. - */ -function testSendPasswordResetEmail_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var expectedEmail = 'user@example.com'; - // Simulate unsuccessful RpcHandler sendPasswordResetEmail. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendPasswordResetEmail', - function(email, actualActionCodeSettings) { - assertObjectEquals({}, actualActionCodeSettings); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.sendPasswordResetEmail(expectedEmail).then(function() { - fail('sendPasswordResetEmail should not resolve!'); - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests sendPasswordResetEmail successful operation with action code settings. - */ -function testSendPasswordResetEmail_actionCodeSettings_success() { - var expectedEmail = 'user@example.com'; - // Simulate successful RpcHandler sendPasswordResetEmail. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendPasswordResetEmail', - function(email, actualActionCodeSettings) { - assertObjectEquals( - new fireauth.ActionCodeSettings(actionCodeSettings).buildRequest(), - actualActionCodeSettings); - assertEquals(expectedEmail, email); - return goog.Promise.resolve(expectedEmail); - }); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.sendPasswordResetEmail(expectedEmail, actionCodeSettings) - .then(function() { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -/** - * Tests sendPasswordResetEmail invalid action code settings. - */ -function testSendPasswordResetEmail_actionCodeSettings_error() { - var settings = { - 'url': '' - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CONTINUE_URI); - var expectedEmail = 'user@example.com'; - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.sendPasswordResetEmail(expectedEmail, settings).then(function() { - fail('sendPasswordResetEmail should not resolve!'); - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyPasswordResetCode successful operation. - */ -function testVerifyPasswordResetCode_success() { - var expectedEmail = 'user@example.com'; - // Valid server response. - var serverResponse = { - kind: 'identitytoolkit#ResetPasswordResponse', - email: expectedEmail, - requestType: 'PASSWORD_RESET' - }; - var expectedCode = 'PASSWORD_RESET_CODE'; - // Simulate successful RpcHandler confirmPasswordReset with no new password. - stubs.replace( - fireauth.RpcHandler.prototype, - 'checkActionCode', - function(code) { - assertEquals(expectedCode, code); - return goog.Promise.resolve(serverResponse); - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.verifyPasswordResetCode(expectedCode) - .then(function(email) { - assertEquals(expectedEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests confirmPasswordReset successful operation. - */ -function testConfirmPasswordReset_success() { - var expectedEmail = 'user@example.com'; - var expectedCode = 'PASSWORD_RESET_CODE'; - var expectedNewPassword = 'newPassword'; - // Simulate successful RpcHandler confirmPasswordReset. - stubs.replace( - fireauth.RpcHandler.prototype, - 'confirmPasswordReset', - function(code, newPassword) { - assertEquals(expectedCode, code); - assertEquals(expectedNewPassword, newPassword); - return goog.Promise.resolve(expectedEmail); - }); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.confirmPasswordReset(expectedCode, expectedNewPassword) - .then(function() { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -/** - * Tests confirmPasswordReset failing operation. - */ -function testConfirmPasswordReset_error() { - var expectedCode = 'PASSWORD_RESET_CODE'; - var expectedNewPassword = 'newPassword'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OOB_CODE); - // Simulate unsuccessful RpcHandler confirmPasswordReset. - stubs.replace( - fireauth.RpcHandler.prototype, - 'confirmPasswordReset', - function(code, newPassword) { - assertEquals(expectedCode, code); - assertEquals(expectedNewPassword, newPassword); - return goog.Promise.reject(expectedError); - }); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.confirmPasswordReset(expectedCode, expectedNewPassword) - .then(function() { - fail('confirmPasswordReset should not resolve!'); - }) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - - -/** - * Tests checkActionCode successful operation. - */ -function testCheckActionCode_success() { - var expectedCode = 'PASSWORD_RESET_CODE'; - var expectedServerResponse = { - kind: 'identitytoolkit#ResetPasswordResponse', - requestType: 'PASSWORD_RESET', - email: 'user@example.com' - }; - var expectedActionCodeInfo = - new fireauth.ActionCodeInfo(expectedServerResponse); - // Simulate successful RpcHandler checkActionCode. - stubs.replace( - fireauth.RpcHandler.prototype, - 'checkActionCode', - function(code) { - assertEquals(expectedCode, code); - return goog.Promise.resolve(expectedServerResponse); - }); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.checkActionCode(expectedCode) - .then(function(info) { - assertObjectEquals(expectedActionCodeInfo, info); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -/** - * Tests checkActionCode failing operation. - */ -function testCheckActionCode_error() { - var expectedCode = 'PASSWORD_RESET_CODE'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OOB_CODE); - // Simulate unsuccessful RpcHandler checkActionCode. - stubs.replace( - fireauth.RpcHandler.prototype, - 'checkActionCode', - function(code) { - assertEquals(expectedCode, code); - return goog.Promise.reject(expectedError); - }); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.checkActionCode(expectedCode) - .then(function() { - fail('checkActionCode should not resolve!'); - }) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -/** - * Tests applyActionCode successful operation. - */ -function testApplyActionCode_success() { - var expectedEmail = 'user@example.com'; - var expectedCode = 'EMAIL_VERIFICATION_CODE'; - // Simulate successful RpcHandler applyActionCode. - stubs.replace( - fireauth.RpcHandler.prototype, - 'applyActionCode', - function(code) { - assertEquals(expectedCode, code); - return goog.Promise.resolve(expectedEmail); - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - assertAuthTokenListenerCalledOnce(auth1); - auth1.applyActionCode(expectedCode).then(function() { - asyncTestCase.signal(); - }); -} - - -/** - * Tests applyActionCode failing operation. - */ -function testApplyActionCode_error() { - var expectedCode = 'EMAIL_VERIFICATION_CODE'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OOB_CODE); - // Simulate unsuccessful RpcHandler applyActionCode. - stubs.replace( - fireauth.RpcHandler.prototype, - 'applyActionCode', - function(code) { - assertEquals(expectedCode, code); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config1, appId1); - auth1 = app1.auth(); - auth1.applyActionCode(expectedCode) - .then(function() { - fail('confirmPasswordReset should not resolve!'); - }, function(error) { - asyncTestCase.signal(); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -/** Replace the OAuth Sign in handler with a fake imitation for testing. */ -function fakeOAuthSignInHandler() { - // Helper function to replace instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) {}, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); -} - - -function testAuth_authEventManager() { - // Test Auth event manager. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - initializeMockStorage(); - var expectedManager = { - 'subscribe': goog.testing.recordFunction(), - 'unsubscribe': goog.testing.recordFunction(), - 'clearRedirectResult': goog.testing.recordFunction() - }; - // Return stub manager. - stubs.replace( - fireauth.AuthEventManager, - 'getManager', - function(authDomain, apiKey, appName) { - assertEquals('subdomain.firebaseapp.com', authDomain); - assertEquals('API_KEY', apiKey); - assertEquals(appId1, appName); - return expectedManager; - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Test manager initialized and Auth subscribed. - auth1.onIdTokenChanged(function(user) { - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertEquals(expectedManager, manager); - assertEquals(0, expectedManager.unsubscribe.getCallCount()); - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals( - auth1, expectedManager.subscribe.getLastCall().getArgument(0)); - assertEquals(0, expectedManager.clearRedirectResult.getCallCount()); - // Delete should trigger unsubscribe and redirect result clearing. - auth1.delete(); - // After destroy, Auth should be unsubscribed. - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals(1, expectedManager.unsubscribe.getCallCount()); - // Redirect result should also be cleared. - assertEquals(1, expectedManager.clearRedirectResult.getCallCount()); - assertEquals( - auth1, expectedManager.unsubscribe.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); -} - - -/** Asserts that AuthEventManager can pass through emulator settings. */ -function testAuth_authEventManager_withEmulator() { - // Test Auth event manager. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - initializeMockStorage(); - var expectedManager = { - 'subscribe': goog.testing.recordFunction(), - 'unsubscribe': goog.testing.recordFunction(), - 'clearRedirectResult': goog.testing.recordFunction() - }; - // Return stub manager. - stubs.replace( - fireauth.AuthEventManager, - 'getManager', - function (authDomain, apiKey, appName, emulatorConfig) { - assertEquals('subdomain.firebaseapp.com', authDomain); - assertEquals('API_KEY', apiKey); - assertEquals(appId1, appName); - assertObjectEquals(emulatorConfig, { - url: 'http://emulator.host:1234', - disableWarnings: false, - }); - return expectedManager; - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - auth1.useEmulator('http://emulator.host:1234'); - // Test manager initialized and Auth subscribed. - auth1.onIdTokenChanged(function (user) { - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name, { - url: 'http://emulator.host:1234', - disableWarnings: false, - }); - assertEquals(expectedManager, manager); - assertEquals(0, expectedManager.unsubscribe.getCallCount()); - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals( - auth1, expectedManager.subscribe.getLastCall().getArgument(0)); - assertEquals(0, expectedManager.clearRedirectResult.getCallCount()); - // Delete should trigger unsubscribe and redirect result clearing. - auth1.delete(); - // After destroy, Auth should be unsubscribed. - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals(1, expectedManager.unsubscribe.getCallCount()); - // Redirect result should also be cleared. - assertEquals(1, expectedManager.clearRedirectResult.getCallCount()); - assertEquals( - auth1, expectedManager.unsubscribe.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); -} - - -function testAuth_signout() { - // Test successful sign out. - fireauth.AuthEventManager.ENABLED = true; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Simulate new token on each call. - var counter = 0; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - // Return new token on each call. - counter++; - return goog.Promise.resolve({ - 'accessToken': 'ID_TOKEN' + counter.toString(), - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Access token unchanged, should trigger notifyAuthListeners_. - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(3); - // Logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Save current user in storage. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - - var savedUser = null; - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set Auth language. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - var authChangeCalled = 0; - // This should trigger initially and then on sign out. - auth1.addAuthTokenListener(function(token) { - authChangeCalled++; - if (authChangeCalled == 1) { - // Save current user. - savedUser = auth1['currentUser']; - assertUserEquals(user1, auth1['currentUser']); - } else if (authChangeCalled == 2) { - assertNull(auth1['currentUser']); - } else { - fail('Auth state change should not trigger more than twice!'); - } - asyncTestCase.signal(); - }); - auth1.signOut().then(function() { - // User should be deleted from storage. - return currentUserStorageManager.getCurrentUser(); - }) - .then(function(user) { - // No user stored anymore. - assertNull(user); - // Current user should be nullified. - assertNull(auth1['currentUser']); - // Language should be set on signed out user. - assertEquals('fr', savedUser.getLanguageCode()); - // Framework updates should still propagate to signed out user. - assertArrayEquals(['firebaseui'], savedUser.getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], savedUser.getFramework()); - // Language updates should still propagate to signed out user. - auth1.languageCode = 'de'; - assertEquals('de', savedUser.getLanguageCode()); - // Refresh token on logged out user. - savedUser.getIdToken().then(function(token) { - // Should not trigger listeners. - assertEquals('ID_TOKEN2', token); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testAuth_initState_signedInStatus() { - // Test init state with previously signed in user. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // New loaded user should be reloaded before being set as current user. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Access token unchanged, should trigger notifyAuthListeners_. - return goog.Promise.resolve(); - }); - // Current user change listener should be added for future changes. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) { - asyncTestCase.signal(); - }); - // Return new token on each request. - var counter = 0; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - // Return new token on each call. - counter++; - return goog.Promise.resolve({ - 'accessToken': 'ID_TOKEN' + counter.toString(), - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - asyncTestCase.waitForSignals(4); - // Logged in user to be detected in initState. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Save signed in user to storage. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - // Before init state current user is null. - assertNull(auth1['currentUser']); - // This should run when signed in user is detected. - var tokenChangeCalls = 0; - auth1.addAuthTokenListener(function(token) { - tokenChangeCalls++; - // Signed in user should be detected. - assertUserEquals(user1, auth1['currentUser']); - // Trigger token change on user, it should be detected by Auth listener - // above. Run only on first call. - if (tokenChangeCalls == 1) { - // Framework should propagate to currentUser. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Language code should propagate to currentUser. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - auth1['currentUser'].getIdToken().then(function(token) { - // Token listener above should have detected this and incremented - // tokenChangeCalls. - assertEquals(2, tokenChangeCalls); - asyncTestCase.signal(); - }); - } - }); - auth1.onIdTokenChanged(function(user) { - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and its current user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - asyncTestCase.signal(); - }); - // User state change triggered with user. - auth1.onAuthStateChanged(function(user) { - assertNotNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_initState_signedInStatus_withEmulator() { - // Test init state with previously signed in user. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function () { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // New loaded user should be reloaded before being set as current user. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function () { - // Access token unchanged, should trigger notifyAuthListeners_. - return goog.Promise.resolve(); - }); - // Listen to calls on RPC Handler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmulatorConfig', - goog.testing.recordFunction( - fireauth.RpcHandler.prototype.updateEmulatorConfig)); - asyncTestCase.waitForSignals(1); - // Logged in user to be detected in initState. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Save signed in user to storage. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager.setCurrentUser(user1).then(function () { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set emulator. - auth1.useEmulator('http://emulator.test.domain:1234'); - // Before init state current user is null. - assertNull(auth1['currentUser']); - // User state change triggered with user. - auth1.onAuthStateChanged(function (user) { - // Signed in user should be detected. - assertUserEquals(user1, auth1['currentUser']); - // Emulator config should propagate to currentUser. - assertEquals( - 3, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); - assertEquals(auth1['currentUser'].getRpcHandler(), - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getThis()); - assertObjectEquals( - { - url: 'http://emulator.test.domain:1234', - disableWarnings: false, - }, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0)); - asyncTestCase.signal(); - }); - }); -} - - -/** - * Test that external changes on a saved user will apply after loading from - * storage and reload on user is called. Confirm the updated user is saved. - */ -function testAuth_initState_reloadUpdate_previousSignedInUser() { - asyncTestCase.waitForSignals(2); - stubs.reset(); - initializeMockStorage(); - // Simulate reload introduced external changes to user. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - assertFalse(this['emailVerified']); - this.updateProperty('emailVerified', true); - this.updateProperty('displayName', 'New Name'); - // Internally calls Auth user listeners. - return goog.Promise.resolve(); - }); - // Create user with emailVerified set to false. - accountInfo['emailVerified'] = false; - accountInfo['displayName'] = 'Previous Name'; - var user = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // Confirm created user has email not verified and old display name. - assertFalse(user['emailVerified']); - assertEquals('Previous Name', user['displayName']); - // Save test user. - currentUserStorageManager.setCurrentUser(user).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This will load user from storage and then call reload on it. - // Confirm that user emailVerified and display name are updated. - auth1.onIdTokenChanged(function(user) { - // These should have updated. - assertTrue(user['emailVerified']); - assertEquals('New Name', user['displayName']); - // Confirm user with verified email and update name is updated in storage - // too. - currentUserStorageManager.getCurrentUser(user).then(function(tempUser) { - assertTrue(tempUser['emailVerified']); - assertEquals('New Name', tempUser['displayName']); - asyncTestCase.signal(); - }); - }); - // User state change triggered with user. - auth1.onAuthStateChanged(function(user) { - assertNotNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_initState_signedInStatus_differentAuthDomain() { - // Test init state with previously signed in user using an authDomain - // different from the current Auth instance authDomain. Its authDomain should - // be overridden with current app authDomain. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // New loaded user should be reloaded before being set as current user. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Access token unchanged, should trigger notifyAuthListeners_. - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Simulate the previously logged in user has a different authDomain. - var user1 = new fireauth.AuthUser( - config4, expectedTokenResponse, accountInfo); - // Auth will modify user to use its authDomain. - var modifiedUser1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Save previous signed in user to storage with different authDomain. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager.setCurrentUser(user1).then(function() { - // App initialized with other authDomain - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - // Before init state current user is null. - assertNull(auth1['currentUser']); - // This should run when signed in user is detected. - auth1.addAuthTokenListener(function(token) { - // Framework should propagate to modified signed in user. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Modified signed in user should be detected. - assertUserEquals(modifiedUser1, auth1['currentUser']); - // Language code should propagate to currentUser. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_initState_signedInStatus_withRedirectUser() { - // Test init state with previously signed in user and a pending signed out - // redirect user. The current user in this case does not have the same - // redirect event ID as the redirected user. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Assume origin is a valid one. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Return new token on each request. - var counter = 0; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - // Return new token on each call. - counter++; - return goog.Promise.resolve({ - 'accessToken': 'ID_TOKEN' + counter.toString(), - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - // New loaded user should be reloaded before being set as current user. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - goog.testing.recordFunction(function() { - return goog.Promise.resolve(); - })); - // Record enablePopupRedirect on user. - stubs.replace( - fireauth.AuthUser.prototype, - 'enablePopupRedirect', - goog.testing.recordFunction( - fireauth.AuthUser.prototype.enablePopupRedirect)); - asyncTestCase.waitForSignals(2); - // Logged in user to be detected in initState. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Only set the event ID on the redirected user. - user2.setRedirectEventId('12345678'); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // Save previous redirect user. - redirectUserStorageManager = new fireauth.storage.RedirectUserManager( - config3['apiKey'] + ':' + appId1); - redirectUserStorageManager.setRedirectUser(user2).then(function() { - // Save signed in user to storage. - return currentUserStorageManager.setCurrentUser(user1); - }).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - // Before init state current user is null. - assertNull(auth1['currentUser']); - // This should run when signed in user is detected. - var tokenChangeCalls = 0; - auth1.addAuthTokenListener(function(token) { - tokenChangeCalls++; - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth, current user and the redirect user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - assertEquals( - 2, fireauth.AuthUser.prototype.enablePopupRedirect.getCallCount()); - // Redirect user. - var redirectUser = - fireauth.AuthUser.prototype.enablePopupRedirect.getLastCall() - .getThis(); - // Confirm redirect user. - assertUserEquals(redirectUser, user2); - // Redirect user subscribed. - assertTrue(manager.isSubscribed(redirectUser)); - // Check redirect event ID on redirect user. - assertEquals('12345678', redirectUser.getRedirectEventId()); - // Signed in user should be detected. - assertUserEquals(user1, auth1['currentUser']); - // Trigger all listeners on user, they should be detected by Auth - // listeners above. Trigger only on first call. - if (tokenChangeCalls == 1) { - // Framework should propagate to redirectUser. - assertArrayEquals(['firebaseui'], redirectUser.getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], redirectUser.getFramework()); - // Language code should propagate to redirectUser. - assertEquals('fr', redirectUser.getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', redirectUser.getLanguageCode()); - auth1['currentUser'].getIdToken().then(function(token) { - // Should trigger a token change, confirming Auth still listening to - // events on this user. - assertEquals(2, tokenChangeCalls); - asyncTestCase.signal(); - }); - // No need to run getRedirectUser below more than once. - return; - } - // Redirect user should not longer be saved in storage. - redirectUserStorageManager.getRedirectUser().then(function(user) { - assertNull(user); - // As no redirect event ID is set on current user, reload should be - // called on the current user loaded from storage. - assertEquals(1, fireauth.AuthUser.prototype.reload.getCallCount()); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testAuth_initState_signedInStatus_withRedirectUser_sameEventId() { - // Test init state with a signed in user that is attempting a redirect - // operation ID. The current user and redirect user will have the same - // redirect event ID. - // This test is needed to confirm that user is not reloaded after loading from - // storage. This is important for reauthenticateWithRedirect as this operation - // could be called to recover before token expiration is detected. - // user.reload() could clear the user from storage, ending up with the user no - // longer being current. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Assume origin is a valid one. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Return new token on each request. - var counter = 0; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - // Return new token on each call. - counter++; - return goog.Promise.resolve({ - 'accessToken': 'ID_TOKEN' + counter.toString(), - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - // New loaded user should not be reloaded before being set as current user. - // This is needed to allow the redirect operation to complete. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - goog.testing.recordFunction(function() { - return goog.Promise.resolve(); - })); - // Record enablePopupRedirect on user. - stubs.replace( - fireauth.AuthUser.prototype, - 'enablePopupRedirect', - goog.testing.recordFunction( - fireauth.AuthUser.prototype.enablePopupRedirect)); - asyncTestCase.waitForSignals(2); - // Logged in user to be detected in initState. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Set redirect event ID on user1 to match user2. - user1.setRedirectEventId('12345678'); - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Set redirect event ID on the redirect user. - user2.setRedirectEventId('12345678'); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // Save previous redirect user. - redirectUserStorageManager = new fireauth.storage.RedirectUserManager( - config3['apiKey'] + ':' + appId1); - redirectUserStorageManager.setRedirectUser(user2).then(function() { - // Save signed in user to storage. - return currentUserStorageManager.setCurrentUser(user1); - }).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - // Before init state current user is null. - assertNull(auth1['currentUser']); - // This should run when signed in user is detected. - var tokenChangeCalls = 0; - auth1.addAuthTokenListener(function(token) { - tokenChangeCalls++; - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth, current user and the redirect user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - assertEquals( - 2, fireauth.AuthUser.prototype.enablePopupRedirect.getCallCount()); - // Redirect user. - var redirectUser = - fireauth.AuthUser.prototype.enablePopupRedirect.getLastCall() - .getThis(); - // Confirm redirect user. - assertUserEquals(redirectUser, user2); - // Redirect user subscribed. - assertTrue(manager.isSubscribed(redirectUser)); - // Check redirect event ID on redirect user. - assertEquals('12345678', redirectUser.getRedirectEventId()); - // Signed in user should be detected. - assertUserEquals(user1, auth1['currentUser']); - // Trigger all listeners on user, they should be detected by auth - // listeners above. Trigger only on first call. - if (tokenChangeCalls == 1) { - // Framework should propagate to redirectUser. - assertArrayEquals(['firebaseui'], redirectUser.getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], redirectUser.getFramework()); - // Language code should propagate to redirectUser. - assertEquals('fr', redirectUser.getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', redirectUser.getLanguageCode()); - auth1['currentUser'].getIdToken().then(function(token) { - // Should trigger a token change, confirming Auth still listening to - // events on this user. - assertEquals(2, tokenChangeCalls); - asyncTestCase.signal(); - }); - // No need to run getRedirectUser below more than once. - return; - } - // Redirect user should not longer be saved in storage. - redirectUserStorageManager.getRedirectUser().then(function(user) { - assertNull(user); - // As redirect event ID is set on the current user to be equal to the - // redirect user event ID, reload should not be called on the current - // user loaded from storage. - assertEquals(0, fireauth.AuthUser.prototype.reload.getCallCount()); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testAuth_initState_signedInStatus_deletedUser() { - // Test init state with previously signed in user that was deleted externally. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - // The typical error returned by reload. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // New loaded user should be reloaded. In this case throw an error. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - // Current user change listener should be added. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(6); - // The current stored user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var storageKey = config3['apiKey'] + ':' + appId1; - // Save signed in user. - currentUserStorageManager = new fireauth.storage.UserManager(storageKey); - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Initially current user is null. - assertNull(auth1['currentUser']); - // Listener triggered on state resolution. - auth1.addAuthTokenListener(function(token) { - // Stored user should be retrieved but then on reload error cleared. - assertNull(auth1['currentUser']); - asyncTestCase.signal(); - currentUserStorageManager.getCurrentUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); - auth1.onIdTokenChanged(function(user) { - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth only should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - asyncTestCase.signal(); - }); - // User state change triggered with no user. - auth1.onAuthStateChanged(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_initState_signedInStatus_offline() { - // Test init state with previously signed in user in offline mode. The user - // should not be deleted and Auth state listener should trigger. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - // Network error typical in offline mode. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // New loaded user should be reloaded. In this case throw an error. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(4); - // Current user change listener should be added. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) { - asyncTestCase.signal(); - }); - // The current stored user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var storageKey = config3['apiKey'] + ':' + appId1; - // Save signed in user. - currentUserStorageManager = new fireauth.storage.UserManager(storageKey); - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - // Initially current user is null. - assertNull(auth1['currentUser']); - // Auth state change listener should trigger too. - auth1.onIdTokenChanged(function(user) { - assertUserEquals(user, auth1['currentUser']); - asyncTestCase.signal(); - }); - // User state change triggered with user. - auth1.onAuthStateChanged(function(user) { - assertNotNull(user); - asyncTestCase.signal(); - }); - // Listener triggered on state resolution. - auth1.onAuthStateChanged(function(token) { - // Stored user should be retrieved and kept. - assertUserEquals(user1, auth1['currentUser']); - // Framework should propagate to currentUser. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Language code should propagate to currentUser. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - currentUserStorageManager.getCurrentUser().then(function(user) { - assertUserEquals(user, auth1['currentUser']); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testAuth_initState_signedOutStatus() { - // Test init state with no user signed in. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - // Return a new token on each request. - var counter = 0; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - // Return new token on each call. - counter++; - return goog.Promise.resolve({ - 'accessToken': 'ID_TOKEN' + counter.toString(), - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Current user change listener should be added. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(4); - // Save signed in user. - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This is not realistic but to show that current user is set to null, set it. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - auth1.setCurrentUser_(user1); - // On state resolution this would trigger. - auth1.addAuthTokenListener(function(token) { - // Current user is null. - assertNull(auth1['currentUser']); - // Confirm no listeners on old user. - user1.getIdToken().then(function(token) { - // Should not trigger listeners. - asyncTestCase.signal(); - }); - }); - auth1.onIdTokenChanged(function(user) { - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth only should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - asyncTestCase.signal(); - }); - // User state change triggered with no user. - auth1.onAuthStateChanged(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); -} - - -function testAuth_syncAuthChanges_sameUser() { - // Test syncAuthChanges with the same user detected. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Save sync listener. - var syncListener = null; - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) { - syncListener = listener; - }); - // Get token would get triggered to refresh token on detected user. - // In this case simulate the token changed on the user. - var counter = 0; - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - // Return new token on each call to force Auth token listeners. - counter++; - return goog.Promise.resolve({ - 'accessToken': 'NEW_ACCESS_TOKEN' + counter.toString(), - 'refreshToken': 'NEW_REFRESH_TOKEN' - }); - }); - var accountInfo1 = { - 'uid': '123456', - 'email': 'user1@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - var accountInfo2 = { - 'uid': '123456', - 'email': 'user2@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - asyncTestCase.waitForSignals(3); - var userChanges = 0; - // The originally logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo1); - // The same user with external changes. - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo2); - // Save signed in user. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // Initially user1 logged in. - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { - // Simulate user2 logged in in another tab. - currentUserStorageManager.setCurrentUser(user2).then(function() { - // Simulate syncing to external changes. - syncListener().then(function() { - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and current user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - // Same user reference remains. - assertEquals(currentUser, auth1['currentUser']); - // User1 should be updated with user2 data. - assertEquals('user2@example.com', auth1['currentUser']['email']); - asyncTestCase.signal(); - }); - }); - var firstCall = true; - // As listeners are still attached and token change triggered, Auth state - // listener should trigger. - auth1.addAuthTokenListener(function(token) { - // Ignore first call when user1 is logged in. - if (firstCall) { - // User1 logged in at this point. - assertUserEquals(user1, auth1['currentUser']); - // Framework set on first user. - assertArrayEquals( - ['firebaseui'], auth1['currentUser'].getFramework()); - // New framework update will be caught by user2. - auth1.logFramework('angularfire'); - // Language should be set on first user. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - // New language code update will be caught by user2. - auth1.languageCode = 'de'; - firstCall = false; - return; - } - // Current user updated with user2 data on next call. - assertUserEquals(user2, auth1['currentUser']); - // Updated framework on new current user. - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Updated language set on new current user. - assertEquals('de', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'ru'; - assertEquals('ru', auth1['currentUser'].getLanguageCode()); - asyncTestCase.signal(); - }); - unsubscribe(); - }); - // Should be called once with the initial user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNotNull(currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_syncAuthChanges_newSignIn() { - // Test syncAuthChanges with a new signed in user. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Save sync listener. - var syncListener = null; - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) { - syncListener = listener; - }); - var accountInfo1 = { - 'uid': '1234', - 'email': 'user1@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - var accountInfo2 = { - 'uid': '5678', - 'email': 'user2@example.com', - 'displayName': 'Jane Doe', - 'emailVerified': false - }; - asyncTestCase.waitForSignals(4); - var userChanges = 0; - // The originally logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo1); - // The external new user to be detected with different UID. - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo2); - // Save new signed in user. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // Initially user1 signed in. - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - // Wait for state to be ready. - var unsubscribe = auth1.onIdTokenChanged(function(user) { - // User 1 should be initially logged in. - assertUserEquals(user1, auth1['currentUser']); - // Simulate syncing to external changes where user 2 is logged in. - currentUserStorageManager.setCurrentUser(user2).then(function() { - syncListener().then(function() { - // User 2 should be current user now. - assertUserEquals(user2, auth1['currentUser']); - // Confirm expected UID and email. - assertEquals('5678', auth1['currentUser']['uid']); - assertEquals('user2@example.com', user2['email']); - // User1 reference still exists. - assertEquals('REFRESH_TOKEN', user1['refreshToken']); - asyncTestCase.signal(); - }); - }); - var firstCall = true; - // notifyAuthListeners_ would be triggered here. - auth1.addAuthTokenListener(function(token) { - // Ignore first call for initial user. - if (firstCall) { - // Expected framework set on first user. - assertArrayEquals( - ['firebaseui'], auth1['currentUser'].getFramework()); - // Expected language set on first user. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - firstCall = false; - return; - } - // User 2 is the signed in user now on next call. - assertUserEquals(user2, auth1['currentUser']); - // Expected framework set on second user. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Expected language set on new current user. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and current user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - asyncTestCase.signal(); - }); - unsubscribe(); - }); - // Should be called twice with the different users. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - if (userChanges == 1) { - assertEquals(user1['uid'], currentUser['uid']); - } else { - assertEquals(user2['uid'], currentUser['uid']); - } - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_syncAuthChanges_newSignIn_differentAuthDomain() { - // Test syncAuthChanges with a new signed in user that has a different - // authDomain than the current app authDomain. The synced user should have its - // authDomain field overridden. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - asyncTestCase.waitForSignals(1); - // The originally logged in user. - // Simulate the new logged in user has a different authDomain. - var user1 = new fireauth.AuthUser( - config4, expectedTokenResponse, accountInfo); - // Auth will modify user to use its authDomain. - var modifiedUser1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // Call sign out to wait for init state to resolve and to ensure no user is - // logged in. - auth1.signOut().then(function(result) { - // Save new signed in user with different authDomain to trigger sync later. - currentUserStorageManager.setCurrentUser(user1).then(function() { - // Simulate storage event using a different authDomain. - var storageEvent = new goog.testing.events.Event( - goog.events.EventType.STORAGE, window); - storageEvent.key = 'firebase:authUser:' + auth1.getStorageKey(); - storageEvent.oldValue = null; - storageEvent.newValue = JSON.stringify(user1.toPlainObject()); - // Add new listener. - auth1.addAuthTokenListener(function(token) { - // Ignore initial state trigger. - if (!token) { - return; - } - // Modified user with app authDomain should be current user now. - assertUserEquals(modifiedUser1, auth1['currentUser']); - // Framework should propagate to currentUser. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Language code should propagate to currentUser. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - asyncTestCase.signal(); - }); - // This should force localstorage sync. - mockLocalStorage.fireBrowserEvent(storageEvent); - }); - }); -} - - -function testAuth_syncAuthChanges_newSignOut() { - // Test syncAuthChanges with a sign out event detected. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Save sync listener. - var syncListener = null; - stubs.replace( - fireauth.storage.UserManager.prototype, - 'addCurrentUserChangeListener', - function(listener) { - syncListener = listener; - }); - var accountInfo1 = { - 'uid': '1234', - 'email': 'user1@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - asyncTestCase.waitForSignals(4); - // The originally logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo1); - var savedUser; - var userChanges = 0; - // Initially user1 signed in. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - // Wait for state to be ready. - var unsubscribe = auth1.onIdTokenChanged(function(user) { - // User 1 should be initially logged in. - assertUserEquals(user1, auth1['currentUser']); - // Simulate syncing to external changes where user 1 is logged out. - currentUserStorageManager.removeCurrentUser().then(function() { - // Simulate syncing to external changes. - syncListener().then(function() { - // Current user should be null now. - assertNull(auth1['currentUser']); - // User1 reference still exists. - assertEquals('REFRESH_TOKEN', user1['refreshToken']); - asyncTestCase.signal(); - }); - }); - var firstCall = true; - // notifyAuthListeners_ would be triggered here. - auth1.addAuthTokenListener(function(token) { - // Ignore first call for initial user. - if (firstCall) { - // Save current user. - savedUser = auth1['currentUser']; - firstCall = false; - return; - } - // Null current user on next call. - assertNull(auth1['currentUser']); - // Framework updates should still propagate to saved signed out user. - assertArrayEquals(['firebaseui'], savedUser.getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], savedUser.getFramework()); - // Language code should propagate to saved signed out user. - assertEquals('fr', savedUser.getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', savedUser.getLanguageCode()); - // Listeners no longer attached to old user. - assertEquals(0, user1.stateChangeListeners_.length); - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and user1 should be subscribed on init even though user1 is no - // longer current. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(savedUser)); - asyncTestCase.signal(); - }); - unsubscribe(); - }); - // Should be called twice: first with the expected user and then null, the - // second time. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - if (userChanges == 1) { - assertEquals(user1['uid'], currentUser['uid']); - } else { - assertNull(currentUser); - } - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_updateCurrentUser_sameApiKey() { - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(5); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - currentUserStorageManager = new fireauth.storage.UserManager( - auth1.getStorageKey()); - auth1.languageCode = 'fr'; - auth1.logFramework('firebaseui'); - var userChanges = 0; - var tokenChanges = 0; - // Token changed handler should be triggered twice. Once on initialization, - // the other one after updating current user. - auth1.onIdTokenChanged(function(user) { - if (user) { - assertEquals(1, tokenChanges); - assertUserEquals(user1, auth1['currentUser']); - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and current user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - asyncTestCase.signal(); - // Confirm new user saved in storage. - currentUserStorageManager.getCurrentUser().then(function(currentUser) { - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - } else { - // Verifies listener is triggered initiallly. - assertEquals(0, tokenChanges); - asyncTestCase.signal(); - } - tokenChanges++; - }); - // Auth state changed handler should be triggered twice. Once on - // initialization, the other one after updating current user. - auth1.onAuthStateChanged(function(currentUser) { - if (currentUser) { - assertEquals(1, userChanges); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - } else { - // Verifies listener is triggered initiallly. - assertEquals(0, userChanges); - // Calls updateCurrentUser after Auth listener being triggered first time. - auth1.updateCurrentUser(user1).then(function() { - assertUserEquals(user1, auth1['currentUser']); - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - asyncTestCase.signal(); - }); - } - userChanges++; - }); -} - - -function testAuth_updateCurrentUser_sameUser() { - // Tests the case that user is saved in storage initially and to update - // current user whose uid is same as the previous signed in user's. Auth state - // changed listener should not be triggered in this case. But user attributes - // should be updated in storage. Token changed listener should be triggered - // twice. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(3); - var accountInfo1 = { - 'uid': '1234', - 'email': 'user1@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - var accountInfo2 = { - 'uid': '1234', - 'email': 'user2@example.com', - 'displayName': 'Jane Doe', - 'emailVerified': false - }; - // The existing user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo1); - // The user to be updated to Auth instance with the same UID as user1. - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo2); - currentUserStorageManager = new fireauth.storage.UserManager( - fireauth.util.createStorageKey(config3['apiKey'], appId1)); - // Save test user. This will be loaded on Auth init. - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var userChanges = 0; - var tokenChanges = 0; - // Token changed listener should be triggered twice, once with original - // user, the second time with updated user. - auth1.onIdTokenChanged(function(user) { - tokenChanges++; - if (tokenChanges == 1) { - // Triggered with original user. - assertUserEquals(user1, user); - asyncTestCase.signal(); - } else if (tokenChanges == 2) { - // Triggered with updated user. - assertUserEquals(user2, user); - asyncTestCase.signal(); - } else { - fail('Token changed listener should only be triggered twice!'); - } - - }); - // Auth state listener should only be triggered once with original user. - auth1.onAuthStateChanged(function(user) { - userChanges++; - if (user) { - // The listener should be triggered once with the original user. - assertEquals(1, userChanges); - assertUserEquals(user1, auth1['currentUser']); - var originUserRef = auth1['currentUser']; - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and current user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - // Confirm original user saved in storage. - currentUserStorageManager.getCurrentUser().then(function(currentUser) { - assertUserEquals(user1, currentUser); - // Update current user. - auth1.updateCurrentUser(user2).then(function() { - // Current user reference should remain the same. - assertEquals(originUserRef, auth1['currentUser']); - // User attribute should be updated. - assertUserEquals(user2, auth1['currentUser']); - currentUserStorageManager.getCurrentUser() - .then(function(currentUser) { - assertUserEquals(user2, currentUser); - asyncTestCase.signal(); - }); - }); - }); - } else { - fail('Auth state changed listener should not be triggered with null!'); - } - }); - }); -} - - -function testAuth_updateCurrentUser_differentApiKey() { - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - goog.testing.recordFunction(function() { - return goog.Promise.resolve(); - })); - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(5); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - currentUserStorageManager = new fireauth.storage.UserManager( - auth1.getStorageKey()); - var configWithDifferentApikey = { - 'apiKey': 'API_KEY2', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId2' - }; - var user1 = new fireauth.AuthUser( - configWithDifferentApikey, expectedTokenResponse, accountInfo); - auth1.languageCode = 'fr'; - auth1.logFramework('firebaseui'); - var userChanges = 0; - var tokenChanges = 0; - // Token changed handler should be triggered twice. Once on initialization, - // the other one after updating current user. - auth1.onIdTokenChanged(function(user) { - if (user) { - assertEquals(1, tokenChanges); - fireauth.common.testHelper.assertUserEqualsInWithDiffApikey( - user1, auth1['currentUser'], 'API_KEY2', 'API_KEY'); - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and current user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - asyncTestCase.signal(); - // Confirm new user saved in storage. - currentUserStorageManager.getCurrentUser().then(function(currentUser) { - fireauth.common.testHelper.assertUserEqualsInWithDiffApikey( - user1, currentUser, 'API_KEY2', 'API_KEY'); - asyncTestCase.signal(); - }); - } else { - // Verifies listener is triggered initiallly. - assertEquals(0, tokenChanges); - asyncTestCase.signal(); - } - tokenChanges++; - }); - // Auth state changed handler should be triggered twice. Once on - // initialization, the other one after updating current user. - auth1.onAuthStateChanged(function(currentUser) { - if (currentUser) { - assertEquals(1, userChanges); - fireauth.common.testHelper.assertUserEqualsInWithDiffApikey( - user1, currentUser, 'API_KEY2', 'API_KEY'); - asyncTestCase.signal(); - } else { - // Verifies listener is triggered initiallly. - assertEquals(0, userChanges); - // Calls updateCurrentUser after Auth listener being triggered first time. - auth1.updateCurrentUser(user1).then(function() { - // If ApiKey is differnt, user needs to be reloaded. - assertEquals(1, fireauth.AuthUser.prototype.reload.getCallCount()); - fireauth.common.testHelper.assertUserEqualsInWithDiffApikey( - user1, auth1['currentUser'], 'API_KEY2', 'API_KEY'); - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - asyncTestCase.signal(); - }); - } - userChanges++; - }); -} - - -function testAuth_updateCurrentUser_differentApiKey_invalidTokenError() { - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - goog.testing.recordFunction(function() { - return goog.Promise.reject(expectedError); - })); - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var configWithDifferentApikey = { - 'apiKey': 'API_KEY2', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId2' - }; - auth1.onIdTokenChanged(function(user) { - if (user) { - fail('ID Token changed listener should not be triggered!'); - } else { - // Verifies the listener is triggered initiallly. - asyncTestCase.signal(); - } - }); - var user1 = new fireauth.AuthUser( - configWithDifferentApikey, expectedTokenResponse, accountInfo); - auth1.updateCurrentUser(user1).thenCatch(function(err) { - fireauth.common.testHelper.assertErrorEquals(expectedError, err); - assertNull(auth1['currentUser']); - asyncTestCase.signal(); - }); -} - - -function testAuth_updateCurrentUser_differentApiKey_tokenExpiresError() { - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - goog.testing.recordFunction(function() { - return goog.Promise.reject(expectedError); - })); - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var configWithDifferentApikey = { - 'apiKey': 'API_KEY2', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId2' - }; - auth1.onIdTokenChanged(function(user) { - if (user) { - fail('ID Token changed listener should not be triggered!'); - } else { - // Verifies the listener is triggered initiallly. - asyncTestCase.signal(); - } - }); - var user1 = new fireauth.AuthUser( - configWithDifferentApikey, expectedTokenResponse, accountInfo); - auth1.updateCurrentUser(user1).thenCatch(function(err) { - fireauth.common.testHelper.assertErrorEquals(expectedError, err); - assertNull(auth1['currentUser']); - asyncTestCase.signal(); - }); -} - - -function testAuth_updateCurrentUser_nullUserError() { - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.NULL_USER); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - auth1.updateCurrentUser(null).thenCatch(function(err) { - fireauth.common.testHelper.assertErrorEquals(expectedError, err); - asyncTestCase.signal(); - }); -} - - -function testAuth_updateCurrentUser_sameApiKeyAndTenantId() { - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(5); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sets tenant ID on Auth instance. - auth1.tenantId = 'TENANT_ID'; - // Sets the tenant ID on user. - accountInfo['tenantId'] = 'TENANT_ID'; - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - currentUserStorageManager = new fireauth.storage.UserManager( - auth1.getStorageKey()); - var userChanges = 0; - var tokenChanges = 0; - // Token changed handler should be triggered twice. Once on initialization, - // the other one after updating current user. - auth1.onIdTokenChanged(function(user) { - if (user) { - assertEquals(1, tokenChanges); - // Verifies that tenant ID is set on current user. - assertEquals('TENANT_ID', auth1['currentUser']['tenantId']); - assertUserEquals(user1, auth1['currentUser']); - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and current user should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - asyncTestCase.signal(); - // Confirm new user saved in storage. - currentUserStorageManager.getCurrentUser().then(function(currentUser) { - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - } else { - // Verifies listener is triggered initiallly. - assertEquals(0, tokenChanges); - asyncTestCase.signal(); - } - tokenChanges++; - }); - // Auth state changed handler should be triggered twice. Once on - // initialization, the other one after updating current user. - auth1.onAuthStateChanged(function(currentUser) { - if (currentUser) { - // Verifies that tenant ID is set on current user. - assertEquals('TENANT_ID', currentUser['tenantId']); - assertEquals(1, userChanges); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - } else { - // Verifies listener is triggered initiallly. - assertEquals(0, userChanges); - // Calls updateCurrentUser after auth listener being triggered first time. - auth1.updateCurrentUser(user1).then(function() { - assertUserEquals(user1, auth1['currentUser']); - asyncTestCase.signal(); - }); - } - userChanges++; - }); -} - - -function testAuth_updateCurrentUser_tenantIdMismatchError() { - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TENANT_ID_MISMATCH); - asyncTestCase.waitForSignals(1); - // Sets the tenant ID on user. - accountInfo['tenantId'] = 'TENANT_ID'; - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - assertEquals('TENANT_ID', user1['tenantId']); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - auth1.tenantId = '456789012312'; - auth1.updateCurrentUser(user1).thenCatch(function(err) { - fireauth.common.testHelper.assertErrorEquals(expectedError, err); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithIdTokenResponse_newUser() { - // Test signInWithIdTokenResponse returning a new user. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Initialize from ID token response should be called and resolved with the - // new signed in user. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse, redirectStorageManager, frameworks) { - // Expected frameworks passed on user initialization. - assertArrayEquals(['firebaseui'], frameworks); - assertObjectEquals(config3, options); - assertObjectEquals(expectedTokenResponse, idTokenResponse); - asyncTestCase.signal(); - return goog.Promise.resolve(user1); - }); - asyncTestCase.waitForSignals(5); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework and test signInWithIdTokenResponse initializes user with the - // correct list of frameworks. - auth1.logFramework('firebaseui'); - var userChanges = 0; - currentUserStorageManager = new fireauth.storage.UserManager( - auth1.getStorageKey()); - // The newly signed in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Run on init only. - var unsubscribe = auth1.onIdTokenChanged(function(user) { - // notifyAuthListeners_ would be triggered here. - auth1.addAuthTokenListener(function(token) { - // Current user set to user1. - assertEquals(user1, auth1['currentUser']); - // Listeners should be attached to new current user. - assertEquals(1, auth1['currentUser'].stateChangeListeners_.length); - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // Auth and user1 should be subscribed. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - asyncTestCase.signal(); - // Confirm new user saved in storage. - currentUserStorageManager.getCurrentUser().then(function(user) { - assertUserEquals(user, user1); - asyncTestCase.signal(); - }); - }); - unsubscribe(); - }); - // User not logged in yet. Run sign in with ID token response. - auth1.signInWithIdTokenResponse(expectedTokenResponse).then(function() { - // Current user should be set to user1. - assertEquals(user1, auth1['currentUser']); - // Framework updates should still propagate to currentUser. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Language code should propagate to currentUser. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - asyncTestCase.signal(); - }); - // Should be called with the expected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertEquals(user1['uid'], currentUser['uid']); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithIdTokenResponse_sameUser() { - // Test signInWithIdTokenResponse returning the same user. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Initialize from ID token response should be called and resolved with the - // new signed in user. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse, redirectStorageManager, frameworks) { - // Expected frameworks passed on user initialization. - assertArrayEquals(['firebaseui'], frameworks); - assertObjectEquals(config3, options); - assertObjectEquals(expectedTokenResponse, idTokenResponse); - asyncTestCase.signal(); - // Return second user which is the same user but with difference account - // info. - return goog.Promise.resolve(user2); - }); - // Simulate user1 initially logged in. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - return goog.Promise.resolve(user1); - }); - // Stub reload. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - var accountInfo1 = { - 'uid': '1234', - 'email': 'user1@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - var accountInfo2 = { - 'uid': '1234', - 'email': 'user2@example.com', - 'displayName': 'Jane Doe', - 'emailVerified': false - }; - asyncTestCase.waitForSignals(5); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework and test signInWithIdTokenResponse initializes user with the - // correct list of frameworks. - auth1.logFramework('firebaseui'); - currentUserStorageManager = new fireauth.storage.UserManager( - auth1.getStorageKey()); - // The existing user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo1); - // The newly signed in user with the same UID as user1. - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo2); - var unsubscribe = auth1.onIdTokenChanged(function(user) { - // Call signInWithIdTokenResponse. This should resolve with same user - // updated. - auth1.signInWithIdTokenResponse( - expectedTokenResponse).then(function() { - // Framework updates should still propagate to currentUser. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Language code should propagate to currentUser. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - // Same reference. - assertEquals(user1, auth1['currentUser']); - // Same properties as user2. - assertUserEquals(user2, auth1['currentUser']); - assertEquals('user2@example.com', auth1['currentUser']['email']); - assertEquals('Jane Doe', auth1['currentUser']['displayName']); - asyncTestCase.signal(); - }); - unsubscribe(); - }); - var firstCall = true; - // notifyAuthListeners_ would be triggered here. - auth1.addAuthTokenListener(function(token) { - // Simulate user1 already signed in. - if (firstCall) { - // User1 logged in at this point. - assertEquals(user1, auth1['currentUser']); - firstCall = false; - return; - } - // User1 still set as current user on next call. - assertEquals(user1, auth1['currentUser']); - // Auth and user1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertFalse(manager.isSubscribed(user2)); - assertTrue(manager.isSubscribed(user1)); - asyncTestCase.signal(); - // Confirm current user saved in storage. - currentUserStorageManager.getCurrentUser().then(function(user) { - assertUserEquals(user, auth1['currentUser']); - asyncTestCase.signal(); - }); - }); - var userChanges = 0; - // Should be called with the expected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertEquals(user1['uid'], currentUser['uid']); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithIdTokenResponse_newUserDifferentFromCurrent() { - // Test signInWithIdTokenResponse returning a new user while a previous user - // existed. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Initialize from ID token response should be called and resolved with the - // new signed in user that is different from the current user. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse, redirectStorageManager, frameworks) { - // Expected frameworks passed on user initialization. - assertArrayEquals(['firebaseui'], frameworks); - assertObjectEquals(config3, options); - assertObjectEquals(expectedTokenResponse, idTokenResponse); - asyncTestCase.signal(); - // Return second user which has a different UID. - return goog.Promise.resolve(user2); - }); - // Simulate user1 initially logged in. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - return goog.Promise.resolve(user1); - }); - // Stub reload. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - // First user account info. - var accountInfo1 = { - 'uid': '1234', - 'email': 'user1@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - // Second user has different UID. - var accountInfo2 = { - 'uid': '5678', - 'email': 'user2@example.com', - 'displayName': 'Jane Doe', - 'emailVerified': false - }; - // Initialize users. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo1); - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo2); - asyncTestCase.waitForSignals(6); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework and test signInWithIdTokenResponse initializes user with the - // correct list of frameworks. - auth1.logFramework('firebaseui'); - currentUserStorageManager = new fireauth.storage.UserManager( - auth1.getStorageKey()); - // signInWithIdTokenResponse should resolve with user2 as currentUser. - var unsubscribe = auth1.onIdTokenChanged(function(user) { - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - // At this stage auth1 and user1 are only subscribed. This will change after - // new sign in. - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - assertFalse(manager.isSubscribed(user2)); - auth1.signInWithIdTokenResponse( - expectedTokenResponse).then(function() { - // Framework updates should still propagate to new currentUser. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], auth1['currentUser'].getFramework()); - // Language code should propagate to new currentUser. - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', auth1['currentUser'].getLanguageCode()); - // User2 is now currentuser. - assertEquals(user2, auth1['currentUser']); - // Same properties as user2. - assertUserEquals(user2, auth1['currentUser']); - assertEquals('5678', auth1['currentUser']['uid']); - assertEquals('user2@example.com', auth1['currentUser']['email']); - assertEquals('Jane Doe', auth1['currentUser']['displayName']); - asyncTestCase.signal(); - }); - unsubscribe(); - }); - var firstCall = true; - // notifyAuthListeners_ would be triggered here. - auth1.addAuthTokenListener(function(token) { - if (firstCall) { - // User1 logged in at this point. - assertEquals(user1, auth1['currentUser']); - firstCall = false; - return; - } - // User2 signed in on next call. - assertEquals(user2, auth1['currentUser']); - // Auth, user1 and user2 should be subscribed even though user1 is no longer - // current. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - assertTrue(manager.isSubscribed(user2)); - asyncTestCase.signal(); - // Confirm new current user saved in storage. Reset getCurrentUser stub - // first. - stubs.reset(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - currentUserStorageManager.getCurrentUser().then(function(user) { - assertUserEquals(user, auth1['currentUser']); - asyncTestCase.signal(); - }); - }); - var userChanges = 0; - // Should be called twice with the expected user each time. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - if (userChanges == 1) { - assertEquals(user1['uid'], currentUser['uid']); - } else { - assertEquals(user2['uid'], currentUser['uid']); - } - asyncTestCase.signal(); - }); -} - - -/** - * Asserts that a new signed in user gets emulator configuration set correctly. - */ -function testAuth_signInWithIdTokenResponse_withEmulator() { - // Test signInWithIdTokenResponse returning a new user. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - stubs.replace( - Date, - 'now', - function () { - return now; - }); - initializeMockStorage(); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Initialize from ID token response should be called and resolved with the - // new signed in user. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function (options, idTokenResponse) { - // Confirm emulatorConfig set on user's app options. - assertObjectEquals(expectedOptions, options); - return goog.Promise.resolve(user1); - }); - asyncTestCase.waitForSignals(1); - var expectedOptions = Object.assign({}, config3); - expectedOptions['emulatorConfig'] = { - url: 'http://emulator.test.domain:1234', - disableWarnings: false, - }; - // The newly signed in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set emulator. - auth1.useEmulator('http://emulator.test.domain:1234'); - // User not logged in yet. Run sign in with ID token response. - // The user should be initialized with the emulator config. - auth1.signInWithIdTokenResponse(expectedTokenResponse).then(function () { - // Current user should be set to user1. - assertEquals(user1, auth1['currentUser']); - asyncTestCase.signal(); - }); -} - - -function testAuth_getIdToken_signedInUser() { - // Tests getIdToken with a signed in user. - fireauth.AuthEventManager.ENABLED = true; - var expectedToken = jwt2; - // Simulate new access token return on a force refresh request to trigger Auth - // state listener. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_refresh) { - // Manual call, force refresh. - if (opt_refresh) { - return goog.Promise.resolve({ - 'accessToken': expectedToken, - 'refreshToken': 'NEW_REFRESH_TOKEN' - }); - } - // Return cached one when called within syncAuthChange. - return goog.Promise.resolve({ - 'accessToken': jwt1, - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Initialize user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - asyncTestCase.waitForSignals(5); - var tokenChangeCalls = 0; - // Set user1 as a signed in user. - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - auth1.addAuthTokenListener(function(token) { - tokenChangeCalls++; - if (tokenChangeCalls == 1) { - // First time called with original token. - assertEquals(expectedTokenResponse['idToken'], token); - } else if (tokenChangeCalls == 2) { - // Second time called with new token. - assertEquals(expectedToken, token); - } else { - fail('Token listener should not be called more than twice.'); - } - // User should be signed in. - assertUserEquals(user1, auth1['currentUser']); - asyncTestCase.signal(); - // Confirm token changes triggered user related changes to be saved. - currentUserStorageManager.getCurrentUser().then(function(user) { - assertUserEquals(user1, auth1['currentUser']); - asyncTestCase.signal(); - }); - }); - // This should return the new STS token. - auth1.getIdTokenInternal(true).then(function(stsResponse) { - assertEquals(expectedToken, stsResponse['accessToken']); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_getIdToken_signedOutUser() { - // Tests getIdToken with a signed out user. - fireauth.AuthEventManager.ENABLED = true; - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) {}, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This should be triggered only on init state. - assertAuthTokenListenerCalledOnce(auth1); - // Since no user is signed in, token should be null. - auth1.getIdTokenInternal(true).then(function(token) { - assertNull(token); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithCustomToken_success() { - // Tests successful signInWithCustomToken. - fireauth.AuthEventManager.ENABLED = true; - var expectedCustomToken = 'CUSTOM_TOKEN'; - var expectedIdToken = fireauth.common.testHelper.createMockJwt({ - 'iss': 'https://securetoken.google.com/12345678', - 'aud': '12345678', - 'auth_time': 1511378629, - 'sub': 'abcdefghijklmnopqrstu', - 'iat': 1511378630, - 'exp': now / 1000 + 3600, - 'firebase': { - 'identities': {}, - 'sign_in_provider': 'custom' - } - }); - expectedTokenResponse['idToken'] = expectedIdToken; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithCustomToken should lead to an Auth user being initialized with - // the returned STS token response. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - // Token response should match RpcHandler response. - assertObjectEquals(expectedTokenResponse, tokenResponse); - // Simulate user sign in completed and returned. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // verifyCustomToken should be called with expected parameters and resolved - // with expected token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyCustomToken', - function(customToken) { - assertEquals(expectedCustomToken, customToken); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTokenResponse); - }); - asyncTestCase.waitForSignals(4); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Set to true for testing to make sure this is changed during processing. - user1.updateProperty('isAnonymous', true); - var expectedResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': {'providerId': null, 'isNewUser': false}, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - currentUserStorageManager = new fireauth.storage.UserManager( - auth1.getStorageKey()); - // Sign in with custom token. - auth1.signInWithCustomToken(expectedCustomToken) - .then(function(result) { - // Anonymous status should be set to false. - assertFalse(result['user']['isAnonymous']); - // Returned user credential should match expected one. - fireauth.common.testHelper.assertUserCredentialResponse( - expectedResult['user'], - expectedResult['credential'], - expectedResult['additionalUserInfo'], - expectedResult['operationType'], - result); - // Confirm anonymous state saved. - currentUserStorageManager.getCurrentUser().then(function(user) { - assertUserEquals(user1, auth1['currentUser']); - assertFalse(user['isAnonymous']); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithCustomToken_error() { - // Tests unsuccessful signInWithCustomToken. - fireauth.AuthEventManager.ENABLED = true; - // Expected rpc error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var expectedCustomToken = 'CUSTOM_TOKEN'; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Error should not lead to user creation. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - fail('signInWithIdTokenResponse should not be called!'); - }); - // verifyCustomToken should be called with expected parameters and throws the - // expected error. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyCustomToken', - function(customToken) { - assertEquals(expectedCustomToken, customToken); - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with custom token should throw the expected error. - auth1.signInWithCustomToken(expectedCustomToken).thenCatch(function(err) { - fireauth.common.testHelper.assertErrorEquals(expectedError, err); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailLink_success() { - // Tests successful signInWithEmailLink. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and link. - var expectedEmail = 'user@example.com'; - var expectedLink = 'https://www.example.com?mode=signIn&oobCode=code' + - '&apiKey=API_KEY'; - var expectedOobCode = 'code'; - var expectedIdToken = 'HEAD.ew0KICAiaXNzIjogImh0dHBzOi8vc2VjdXJldG9rZW4uZ2' + - '9vZ2xlLmNvbS8xMjM0NTY3OCIsDQogICJwaWN0dXJlIjogImh0dHBzOi8vcGx1cy5nb29' + - 'nbGUuY29tL2FiY2RlZmdoaWprbG1ub3BxcnN0dSIsDQogICJhdWQiOiAiMTIzNDU2Nzgi' + - 'LA0KICAiYXV0aF90aW1lIjogMTUxMDM1NzYyMiwNCiAgInVzZXJfaWQiOiAiYWJjZGVmZ' + - '2hpamtsbW5vcHFyc3R1IiwNCiAgInN1YiI6ICJhYmNkZWZnaGlqa2xtbm9wcXJzdHUiLA' + - '0KICAiaWF0IjogMTUxMDM1NzYyMiwNCiAgImV4cCI6IDE1MTAzNjEyMjIsDQogICJlbWF' + - 'pbCI6ICJ1c2VyQGV4YW1wbGUuY29tIiwNCiAgImVtYWlsX3ZlcmlmaWVkIjogdHJ1ZSwN' + - 'CiAgImZpcmViYXNlIjogew0KICAgICJpZGVudGl0aWVzIjogew0KICAgICAgImVtYWlsI' + - 'jogWw0KICAgICAgICAidXNlckBleGFtcGxlLmNvbSINCiAgICAgIF0NCiAgICB9LA0KIC' + - 'AgICJzaWduX2luX3Byb3ZpZGVyIjogInBhc3N3b3JkIg0KICB9DQp9.SIGNATURE'; - expectedTokenResponse['idToken'] = expectedIdToken; - - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected - // token response generated by RPC response. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - // Token response should match rpcHandler response. - assertObjectEquals(expectedTokenResponse, tokenResponse); - // Simulate user sign in completed and returned. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // emailLinkSignIn should be called with expected parameters and resolved - // with expected token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'emailLinkSignIn', - function(email, oobCode) { - assertEquals(expectedEmail, email); - assertEquals(expectedOobCode, oobCode); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTokenResponse); - }); - asyncTestCase.waitForSignals(3); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var expectedResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': {'providerId': 'password', 'isNewUser': false}, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with email and link. - auth1.signInWithEmailLink(expectedEmail, expectedLink) - .then(function(result) { - assertObjectEquals(expectedResult, result); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailLink_success_tenantId() { - // Tests successful signInWithEmailLink with tenant ID. - const expectedEmail = 'user@example.com'; - // Sign-in email link with tenant ID. - const expectedLink = 'https://www.example.com?mode=signIn&oobCode=code' + - '&apiKey=API_KEY&tenantId=TENANT_ID'; - const expectedOobCode = 'code'; - - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - const emailLinkSignIn = - mockControl.createMethodMock(auth1.getRpcHandler(), 'emailLinkSignIn'); - emailLinkSignIn(expectedEmail, expectedOobCode).$once() - .$returns(goog.Promise.resolve(expectedTokenResponse4)); - mockControl.$replayAll(); - asyncTestCase.waitForSignals(1); - - // Set tenant ID on Auth. - auth1.tenantId = 'TENANT_ID'; - // Verify that tenant ID is set on Rpc handler. - assertEquals('TENANT_ID', auth1.getRpcHandler().getTenantId()); - // Sign in with email and link. - return auth1.signInWithEmailLink(expectedEmail, expectedLink) - .then((result) => { - fireauth.common.testHelper.assertUserCredentialResponse( - auth1.currentUser, - null, - {'providerId': 'password', 'isNewUser': false}, - fireauth.constants.OperationType.SIGN_IN, - result); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailLink_error_tenantIdMismatch() { - // Tests when the tenant ID in link doesn't match the tenant ID on - // Auth instance. - const expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TENANT_ID_MISMATCH); - fireauth.AuthEventManager.ENABLED = true; - const expectedEmail = 'user@example.com'; - // Link with TENANT_ID1. - const expectedLink = 'https://www.example.com?mode=signIn&oobCode=code' + - '&apiKey=API_KEY&tenantId=TENANT_ID1'; - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set the tenant ID to a different tenant ID from the link. - auth1.tenantId = 'TENANT_ID2'; - // Sign in with email and link. - auth1.signInWithEmailLink(expectedEmail, expectedLink) - .thenCatch((error) => { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailLink_error_tenantIdMismatch_nullTenantId() { - // Tests when the tenant ID is provided in the link but is set to null on - // Auth instance. - const expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TENANT_ID_MISMATCH); - fireauth.AuthEventManager.ENABLED = true; - const expectedEmail = 'user@example.com'; - // Link with TENANT_ID1. - const expectedLink = 'https://www.example.com?mode=signIn&oobCode=code' + - '&apiKey=API_KEY&tenantId=TENANT_ID1'; - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set the tenant ID to null on Auth instance. - auth1.tenantId = null; - // Sign in with email and link. - auth1.signInWithEmailLink(expectedEmail, expectedLink) - .thenCatch((error) => { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailLink_deepLink_success() { - // Tests successful signInWithEmailLink where the URL provided is the entire - // FDL link, instead of the deep link. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and link. - var expectedEmail = 'user@example.com'; - var deepLink = 'https://www.example.com?mode=signIn&oobCode=code' + - '&apiKey=API_KEY'; - var expectedLink = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink); - var expectedOobCode = 'code'; - - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected - // token response generated by RPC response. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - // Token response should match rpcHandler response. - assertObjectEquals(expectedTokenResponse4, tokenResponse); - // Simulate user sign in completed and returned. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // emailLinkSignIn should be called with expected parameters and resolved - // with expected token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'emailLinkSignIn', - function(email, oobCode) { - assertEquals(expectedEmail, email); - assertEquals(expectedOobCode, oobCode); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTokenResponse4); - }); - asyncTestCase.waitForSignals(3); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse4, accountInfo); - var expectedResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': {'providerId': 'password', 'isNewUser': false}, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with email and link. - auth1.signInWithEmailLink(expectedEmail, expectedLink) - .then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - expectedResult['user'], - expectedResult['credential'], - expectedResult['additionalUserInfo'], - expectedResult['operationType'], - result); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailLink_error() { - // Tests successful signInWithEmailLink. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and link. - var expectedEmail = 'user@example.com'; - var expectedLink = 'https://www.example.com?mode=signIn&oobCode=code' + - '&apiKey=API_KEY'; - var expectedOobCode = 'code'; - // Expected RPC error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected - // token response generated by RPC response. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - fail('signInWithIdTokenResponse should not be called!'); - }); - // emailLinkSignIn should be called with expected parameters and resolved - // with expected error. - stubs.replace( - fireauth.RpcHandler.prototype, - 'emailLinkSignIn', - function(email, oobCode) { - assertEquals(expectedEmail, email); - assertEquals(expectedOobCode, oobCode); - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with email and link should throw expected error. - auth1.signInWithEmailLink(expectedEmail, expectedLink) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailLink_invalidLink_error() { - // Tests signInWithEmailLink when an invalid link is provided. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and link. - var expectedEmail = 'user@example.com'; - var expectedLink = 'https://www.example.com?mode=signIn'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, 'Invalid email link!'); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with email and link should throw expected error. - auth1.signInWithEmailLink(expectedEmail, expectedLink) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailAndPassword_success() { - // Tests successful signInWithEmailAndPassword. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and password. - var expectedEmail = 'user@example.com'; - var expectedPass = 'password'; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected - // token response generated by RPC response. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - // Token response should match rpchandler response. - assertObjectEquals(expectedTokenResponse4, tokenResponse); - // Simulate user sign in completed and returned. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // verifyPassword should be called with expected parameters and resolved - // with expected token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyPassword', - function(email, password) { - assertEquals(expectedEmail, email); - assertEquals(expectedPass, password); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTokenResponse4); - }); - asyncTestCase.waitForSignals(3); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse4, accountInfo); - var expectedResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': {'providerId': 'password', 'isNewUser': false}, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with email and password. - auth1.signInWithEmailAndPassword(expectedEmail, expectedPass) - .then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - expectedResult['user'], - expectedResult['credential'], - expectedResult['additionalUserInfo'], - expectedResult['operationType'], - result); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithEmailAndPassword_error() { - // Tests unsuccessful signInWithEmailAndPassword. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and password. - var expectedEmail = 'user@example.com'; - var expectedPass = 'password'; - // Expected RPC error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should not be called due to RPC error. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - fail('signInWithIdTokenResponse should not be called!'); - }); - // verifyPassword should be called with expected parameters and resolved - // with expected error. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyPassword', - function(email, password) { - assertEquals(expectedEmail, email); - assertEquals(expectedPass, password); - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with email and password should throw expected error. - auth1.signInWithEmailAndPassword(expectedEmail, expectedPass) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_createUserWithEmailAndPassword_success() { - // Tests successful createUserWithEmailAndPassword. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and password. - var expectedEmail = 'user@example.com'; - var expectedPass = 'password'; - expectedTokenResponse4['kind'] = 'identitytoolkit#SignupNewUserResponse'; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected - // token response generated by RPC response. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - // Token response should match rpchandler response. - assertObjectEquals(expectedTokenResponse4, tokenResponse); - // Simulate user sign in completed and returned. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // createAccount should be called with expected parameters and resolved - // with expected token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'createAccount', - function(email, password) { - assertEquals(expectedEmail, email); - assertEquals(expectedPass, password); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTokenResponse4); - }); - asyncTestCase.waitForSignals(3); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse4, accountInfo); - var expectedResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': {'providerId': 'password', 'isNewUser': true}, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - auth1.createUserWithEmailAndPassword(expectedEmail, expectedPass) - .then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - expectedResult['user'], - expectedResult['credential'], - expectedResult['additionalUserInfo'], - expectedResult['operationType'], - result); - asyncTestCase.signal(); - }); -} - - -function testAuth_createUserWithEmailAndPassword_error() { - // Tests unsuccessful createUserWithEmailAndPassword. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and password. - var expectedEmail = 'user@example.com'; - var expectedPass = 'password'; - // Expected RPC error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should not be called due to RPC error. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - fail('signInWithIdTokenResponse should not be called!'); - }); - // createAccount should be called with expected parameters and resolved - // with expected error. - stubs.replace( - fireauth.RpcHandler.prototype, - 'createAccount', - function(email, password) { - assertEquals(expectedEmail, email); - assertEquals(expectedPass, password); - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // createUserWithEmailAndPassword should throw the expected error. - auth1.createUserWithEmailAndPassword(expectedEmail, expectedPass) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInAndRetrieveDataWithCredential_success() { - // Stub signInWithCredential and confirm same response is used for - // signInAndRetrieveDataWithCredential. - // Record deprecation warning calls. - stubs.replace( - fireauth.deprecation, - 'log', - goog.testing.recordFunction()); - stubs.replace( - fireauth.Auth.prototype, - 'signInWithCredential', - function(cred) { - assertEquals(expectedGoogleCredential, cred); - return goog.Promise.resolve(expectedResponse); - }); - fireauth.AuthEventManager.ENABLED = true; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Expected response. Only the user will be returned. - var expectedResponse = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - // signInAndRetrieveDataWithCredential using Google OAuth credential. - auth1.signInAndRetrieveDataWithCredential(expectedGoogleCredential) - .then(function(userCredential) { - // Confirm expected response. - assertEquals(expectedResponse, userCredential); - asyncTestCase.signal(); - }); - // Confirm warning shown. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.deprecation.log.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals( - fireauth.deprecation.Deprecations.SIGN_IN_WITH_CREDENTIAL, - fireauth.deprecation.log.getLastCall().getArgument(0)); -} - - -function testAuth_signInAndRetrieveDataWithCredential_error() { - // Stub signInWithCredential and confirm same error is thrown for - // signInAndRetrieveDataWithCredential. - // Record deprecation warning calls. - stubs.replace( - fireauth.deprecation, - 'log', - goog.testing.recordFunction()); - stubs.replace( - fireauth.Auth.prototype, - 'signInWithCredential', - function(cred) { - assertEquals(expectedGoogleCredential, cred); - return goog.Promise.reject(expectedError); - }); - fireauth.AuthEventManager.ENABLED = true; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.NEED_CONFIRMATION); - // signInAndRetrieveDataWithCredential using Google OAuth credential. - auth1.signInAndRetrieveDataWithCredential(expectedGoogleCredential) - .thenCatch(function(error) { - // Confirm expected error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // Confirm warning shown. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.deprecation.log.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals( - fireauth.deprecation.Deprecations.SIGN_IN_WITH_CREDENTIAL, - fireauth.deprecation.log.getLastCall().getArgument(0)); -} - - -function testAuth_signInWithCredential_success() { - // Tests successful signInWithCredential using OAuth credential. - fireauth.AuthEventManager.ENABLED = true; - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - 'requestUri': 'http://localhost', - 'postBody': 'id_token=googleIdToken&access_token=googleAccessToke' + - 'n&providerId=' + fireauth.idp.ProviderId.GOOGLE - }, - data); - // Resolve with expected token response. - return goog.Promise.resolve(expectedTokenResponseWithIdPData); - }); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected token - // response generated by RPC response. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - // Confirm config options. - assertObjectEquals(config3, options); - // Token response should match rpchandler response. - assertObjectEquals(expectedTokenResponseWithIdPData, idTokenResponse); - // Return expected user. - return goog.Promise.resolve(user1); - }); - // Record calls to signInWithIdTokenResponse. This will help us confirm - // that the expected user is being initialized, set to currentUser, saved - // to storage and token changes triggered. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - goog.testing.recordFunction( - fireauth.Auth.prototype.signInWithIdTokenResponse)); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // signInWithCredential using Google OAuth credential. - auth1.signInWithCredential(expectedGoogleCredential) - .then(function(result) { - // Confirm fireauth.Auth.prototype.signInWithIdTokenResponse is called - // underneath. This will save the new user in storage and trigger auth - // token changes. - assertEquals( - 1, - fireauth.Auth.prototype.signInWithIdTokenResponse.getCallCount()); - assertObjectEquals( - expectedTokenResponseWithIdPData, - fireauth.Auth.prototype.signInWithIdTokenResponse - .getLastCall().getArgument(0)); - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - auth1.currentUser, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - result); - // Confirm user1 set as currentUser. - assertEquals( - user1, - auth1.currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithCredential_nonhttp_success() { - // Tests successful signInWithCredential using OAuth credential in a non - // HTTP environment. - fireauth.AuthEventManager.ENABLED = true; - // Non http or https environment. - stubs.replace( - fireauth.util, - 'getCurrentUrl', - function() {return 'chrome-extension://SOME_LONG_ID';}); - stubs.replace( - fireauth.util, - 'getCurrentScheme', - function() {return 'chrome-extension:';}); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - // requestUri should be localhost even though the current URL and - // scheme are non http. - 'requestUri': 'http://localhost', - 'postBody': 'id_token=googleIdToken&access_token=googleAccessToke' + - 'n&providerId=' + fireauth.idp.ProviderId.GOOGLE - }, - data); - // Resolve with expected token response. - return goog.Promise.resolve(expectedTokenResponseWithIdPData); - }); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected token - // response generated by RPC response. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - // Confirm config options. - assertObjectEquals(config3, options); - // Token response should match rpchandler response. - assertObjectEquals(expectedTokenResponseWithIdPData, idTokenResponse); - // Return expected user. - return goog.Promise.resolve(user1); - }); - // Record calls to signInWithIdTokenResponse. This will help us confirm - // that the expected user is being initialized, set to currentUser, saved - // to storage and token changes triggered. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - goog.testing.recordFunction( - fireauth.Auth.prototype.signInWithIdTokenResponse)); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // signInWithCredential using Google OAuth credential. - auth1.signInWithCredential(expectedGoogleCredential) - .then(function(result) { - // Confirm fireauth.Auth.prototype.signInWithIdTokenResponse is called - // underneath. This will save the new user in storage and trigger Auth - // token changes. - assertEquals( - 1, - fireauth.Auth.prototype.signInWithIdTokenResponse.getCallCount()); - assertObjectEquals( - expectedTokenResponseWithIdPData, - fireauth.Auth.prototype.signInWithIdTokenResponse - .getLastCall().getArgument(0)); - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - auth1.currentUser, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - result); - // Confirm user1 set as currentUser. - assertEquals( - user1, - auth1.currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithCredential_emailPassCredential() { - // Tests successful signInWithCredential using email and password credential. - fireauth.AuthEventManager.ENABLED = true; - // Expected email and password. - var expectedEmail = 'user@example.com'; - var expectedPass = 'password'; - // Simulate successful RpcHandler verifyPassword with expected parameters - // passed and expected token response returned. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyPassword', - function(email, password) { - assertEquals(expectedEmail, email); - assertEquals(expectedPass, password); - // No credential or additional user info returned. - return goog.Promise.resolve(expectedTokenResponse); - }); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected token - // response generated by RPC response. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - // Confirm config options. - assertObjectEquals(config3, options); - // Token response should match rpchandler response. - assertObjectEquals(expectedTokenResponse, idTokenResponse); - // Return expected user. - return goog.Promise.resolve(user1); - }); - // Record calls to signInWithIdTokenResponse. This will help us confirm - // that the expected user is being initialized, set to currentUser, saved - // to storage and token changes triggered. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - goog.testing.recordFunction( - fireauth.Auth.prototype.signInWithIdTokenResponse)); - asyncTestCase.waitForSignals(1); - // Initialize expected user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Email/password credential. - var cred = fireauth.EmailAuthProvider.credential(expectedEmail, expectedPass); - // signInWithCredential using email and password credential. - auth1.signInWithCredential(cred) - .then(function(result) { - // Confirm fireauth.Auth.prototype.signInWithIdTokenResponse is called - // underneath. This will save the new user in storage and trigger Auth - // token changes. - assertEquals( - 1, - fireauth.Auth.prototype.signInWithIdTokenResponse.getCallCount()); - assertObjectEquals( - expectedTokenResponse, - fireauth.Auth.prototype.signInWithIdTokenResponse - .getLastCall().getArgument(0)); - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - auth1.currentUser, - // Expected credential returned, null for password credential. - null, - // Expected additional user info, null for password credential. - null, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - result); - // Confirm user1 set as currentUser. - assertEquals( - user1, - auth1.currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithCredential_error() { - // Tests unsuccessful signInWithCredential. - fireauth.AuthEventManager.ENABLED = true; - // Expected rpc error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.NEED_CONFIRMATION); - // Simulate unsuccessful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - // Confirm expected parameters. - assertObjectEquals( - { - 'requestUri': 'http://localhost', - 'postBody': 'id_token=googleIdToken&access_token=googleAccessToke' + - 'n&providerId=' + fireauth.idp.ProviderId.GOOGLE - }, - data); - // Reject with expected error. - return goog.Promise.reject(expectedError); - }); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should not be called due to RPC error. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - fail('signInWithIdTokenResponse should not be called'); - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // signInWithCredential with a Google credential should throw - // the expected error. - auth1.signInWithCredential(expectedGoogleCredential) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInAnonymously_success() { - // Tests successful signInAnonymously. - var expectedIdToken = fireauth.common.testHelper.createMockJwt({ - 'iss': 'https://securetoken.google.com/12345678', - 'provider_id': 'anonymous', - 'aud': '12345678', - 'auth_time': 1510874749, - 'sub': 'abcdefghijklmnopqrstu', - 'iat': 1510874749, - 'exp': now / 1000 + 3600, - 'firebase': { - 'identities': {}, - 'sign_in_provider': 'anonymous' - } - }); - expectedTokenResponse['idToken'] = expectedIdToken; - expectedTokenResponse['kind'] = 'identitytoolkit#SignupNewUserResponse'; - fireauth.AuthEventManager.ENABLED = true; - // Simulate successful RpcHandler signInAnonymously resolving with expected - // token response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'signInAnonymously', - function() { - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTokenResponse); - }); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should initialize a user using the expected token - // response generated by RPC response. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - // Token response should match RpcHandler response. - assertObjectEquals(expectedTokenResponse, tokenResponse); - // Simulate user sign in completed and returned. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(4); - // Initialize expected anonymous user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var expectedResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': {'providerId': null, 'isNewUser': true}, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - currentUserStorageManager = new fireauth.storage.UserManager( - auth1.getStorageKey()); - auth1.signInAnonymously().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - expectedResult['user'], - expectedResult['credential'], - expectedResult['additionalUserInfo'], - expectedResult['operationType'], - result); - assertTrue(result['user']['isAnonymous']); - // Confirm anonymous state saved. - currentUserStorageManager.getCurrentUser().then(function(user) { - assertUserEquals(user1, auth1['currentUser']); - assertTrue(user['isAnonymous']); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInAnonymously_anonymousUserAlreadySignedIn() { - // Tests signInAnonymously when an anonymous user is already signed in. - var expectedIdToken = fireauth.common.testHelper.createMockJwt({ - 'iss': 'https://securetoken.google.com/12345678', - 'provider_id': 'anonymous', - 'aud': '12345678', - 'auth_time': 1510874749, - 'sub': 'abcdefghijklmnopqrstu', - 'iat': 1510874749, - 'exp': now / 1000 + 3600, - 'firebase': { - 'identities': {}, - 'sign_in_provider': 'anonymous' - } - }); - expectedTokenResponse['idToken'] = expectedIdToken; - fireauth.AuthEventManager.ENABLED = true; - // Simulate successful RpcHandler signInAnonymously. - stubs.replace( - fireauth.RpcHandler.prototype, - 'signInAnonymously', - function() { - fail('signInAnonymously on RpcHandler should not be called!'); - }); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Anonymous user is already signed in so there is no need for this to run. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - fail('signInWithIdTokenResponse should not be called!'); - }); - asyncTestCase.waitForSignals(3); - // Initialize an anonymous user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - user1.updateProperty('isAnonymous', true); - var expectedResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': {'providerId': null, 'isNewUser': false}, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - // Current user reference. - var currentUser = null; - // User state changed counter. - var stateChanged = 0; - // ID token changed counter. - var idTokenChanged = 0; - // Storage key. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // Save anonymous user as current in storage. - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // All listeners should be called once with the saved anonymous user. - auth1.onAuthStateChanged(function(user) { - stateChanged++; - assertEquals(1, stateChanged); - assertEquals(user1['uid'], user['uid']); - asyncTestCase.signal(); - }); - auth1.onIdTokenChanged(function(user) { - idTokenChanged++; - assertEquals(1, idTokenChanged); - assertEquals(user1['uid'], user['uid']); - asyncTestCase.signal(); - }); - // signInAnonymously should resolve with the already signed in anonymous - // user without calling RPC handler underneath. - return auth1.signInAnonymously(); - }).then(function(result) { - assertUserEquals(expectedResult['user'], result['user']); - assertObjectEquals( - expectedResult['additionalUserInfo'], result['additionalUserInfo']); - assertEquals(expectedResult['operationType'], result['operationType']); - assertUserEquals(expectedResult['user'], auth1.currentUser); - assertTrue(result['user']['isAnonymous']); - // Save reference to current user. - currentUser = auth1.currentUser; - // Sign in anonymously again. - return auth1.signInAnonymously(); - }).then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - currentUser, - expectedResult['credential'], - expectedResult['additionalUserInfo'], - expectedResult['operationType'], - result); - // Exact same reference should be returned. - assertEquals(auth1.currentUser, result['user']); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInAnonymously_error() { - // Tests unsuccessful signInAnonymously. - fireauth.AuthEventManager.ENABLED = true; - // Expected RPC error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - // Simulate unsuccessful RpcHandler signInAnonymously throwing the expected - // error. - stubs.replace( - fireauth.RpcHandler.prototype, - 'signInAnonymously', - function() { - asyncTestCase.signal(); - // Trigger invalid response error. - return goog.Promise.reject(expectedError); - }); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // signInWithIdTokenResponse should not be called due to RPC error. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithIdTokenResponse', - function(tokenResponse) { - fail('signInWithIdTokenResponse should not be called'); - }); - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // signInAnonymously should fail with expected error. - auth1.signInAnonymously().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_finishPopupAndRedirectSignIn_success_withoutPostBody() { - // Test successful finishPopupAndRedirectSignIn with Auth credential. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(3); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTokenResponseWithIdPData); - }); - // Simulate Auth user successfully initialized from - // finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - assertObjectEquals(config3, options); - assertObjectEquals(expectedTokenResponseWithIdPData, idTokenResponse); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedUser); - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var expectedUser = new fireauth.AuthUser( - config3, - expectedTokenResponseWithIdPData, - {'uid': 'USER_ID', 'email': 'user@example.com'}); - // This should resolve with expected cred and Auth user. - auth1.finishPopupAndRedirectSignIn('REQUEST_URI', 'SESSION_ID', null, null) - .then(function(response) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - expectedUser, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - response); - asyncTestCase.signal(); - }); -} - - -function testAuth_finishPopupAndRedirectSignIn_success_withPostBody() { - // Test successful finishPopupAndRedirectSignIn with Auth credential where - // Auth event has POST body content. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(3); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': 'POST_BODY', - 'tenantId': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedSamlTokenResponseWithIdPData); - }); - // Simulate Auth user successfully initialized from - // finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - assertObjectEquals(config3, options); - assertObjectEquals( - expectedSamlTokenResponseWithIdPData, idTokenResponse); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedUser); - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var expectedUser = new fireauth.AuthUser( - config3, - expectedSamlTokenResponseWithIdPData, - {'uid': 'USER_ID', 'email': 'user@example.com'}); - // This should resolve with expected cred and Auth user. - auth1.finishPopupAndRedirectSignIn( - 'REQUEST_URI', 'SESSION_ID', null, 'POST_BODY') - .then(function(response) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - expectedUser, - // Expected credential returned. - null, - // Expected additional user info. - expectedSamlAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - response); - asyncTestCase.signal(); - }); -} - - -function testAuth_finishPopupAndRedirectSignIn_success_tenantId() { - // Test successful finishPopupAndRedirectSignIn with Auth credential and - // tenant ID. - // Verify that the tenant ID is passed to RPC handler and the user with tenant - // ID is returned. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(3); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': 'TENANT_ID' - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTokenResponseWithIdPData); - }); - // Simulate Auth user successfully initialized from - // finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - assertObjectEquals(config3, options); - assertObjectEquals(expectedTokenResponseWithIdPData, idTokenResponse); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedTenantUser); - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var expectedTenantUser = new fireauth.AuthUser( - config3, - expectedTokenResponseWithIdPData, - { - 'uid': 'USER_ID', - 'email': 'user@example.com', - 'tenantId': 'TENANT_ID' - }); - // This should resolve with expected cred and Auth user. - auth1.finishPopupAndRedirectSignIn( - 'REQUEST_URI', 'SESSION_ID', 'TENANT_ID', null) - .then(function(response) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - expectedTenantUser, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - response); - asyncTestCase.signal(); - }); -} - - -function testAuth_finishPopupAndRedirectSignIn_noCredential() { - // Test successful finishPopupAndRedirectSignIn with no Auth credential. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(3); - // Expected response does not contain Auth credential. - var expectedResponse = { - 'idToken': jwt1, - 'refreshToken': 'REFRESH_TOKEN', - 'providerId': 'google.com' - }; - // Add Additional IdP data. - expectedResponse['rawUserInfo'] = - expectedTokenResponseWithIdPData['rawUserInfo']; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedResponse); - }); - // Simulate Auth user successfully initialized from - // finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - assertObjectEquals(config3, options); - assertObjectEquals(expectedResponse, idTokenResponse); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedUser); - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var expectedUser = new fireauth.AuthUser( - config3, - expectedResponse, - {'uid': 'USER_ID', 'email': 'user@example.com'}); - // This should resolve with expected user and credential. - auth1.finishPopupAndRedirectSignIn('REQUEST_URI', 'SESSION_ID', null, null) - .then(function(response) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - expectedUser, - // Expected credential returned. - null, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - response); - asyncTestCase.signal(); - }); -} - - -function testAuth_finishPopupAndRedirectSignIn_error() { - // Test finishPopupAndRedirectSignIn verifyAssertion error. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(2); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Simulate RpcHandler verifyAssertion returning an error. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var unsubscribe = auth1.onIdTokenChanged(function(user) { - // This should catch the expected error. - auth1.finishPopupAndRedirectSignIn('REQUEST_URI', 'SESSION_ID', null, null) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - unsubscribe(); - }); -} - - -function testAuth_signInWithPopup_emailCredentialError() { - // Test when sign in with popup verifyAssertion throws an Auth email - // credential error. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(1); - var expectedPopup = { - 'close': function() {} - }; - // Record handler as it should be triggered after the popup is processed. - var recordedHandler = null; - // Expected Auth email credential error. - var credential = - fireauth.GoogleAuthProvider.credential(null, 'ACCESS_TOKEN'); - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential - }, - 'Account already exists, please confirm and link.'); - var expectedEventId = '1234'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config3['authDomain'], - config3['apiKey'], - appId1, - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // Save Auth event handler for later. - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config3['authDomain'], domain); - assertEquals(config3['apiKey'], apiKey); - assertEquals(appId1, name); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Simulate Auth email credential error thrown by verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - return goog.Promise.reject(expectedError); - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var unsubscribe = auth1.onIdTokenChanged(function(user) { - // signInWithPopup should fail with the expected Auth email credential - // error. - auth1.signInWithPopup(expectedProvider) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - unsubscribe(); - }); -} - - -function testAuth_signInWithPopup_success_withoutPostBody() { - // Test successful sign in with popup. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - var recordedHandler = null; - var expectedEventId = '1234'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.waitForSignals(5); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var provider = new fireauth.GoogleAuthProvider(); - // This should resolve with a null result. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Sign in with popup should resolve with expected result. - auth1.signInWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_success_tenantId() { - // Test successful sign in with popup with tenant ID. - // Verify that tenant ID is passed to OAuth sign in handler and - // finishPopupAndRedirectSignIn is called with expected tenantId. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - var recordedHandler = null; - var expectedTenantId = 'TENANT_ID'; - var expectedEventId = '1234'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected, - actualTenantId) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - assertEquals(expectedTenantId, actualTenantId); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - var tenantUser = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfoWithTenantId); - var expectedPopupResult = { - 'user': tenantUser, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.waitForSignals(5); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - auth1.tenantId = expectedTenantId; - var provider = new fireauth.GoogleAuthProvider(); - // This should resolve with a null result. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Sign in with popup should resolve with expected result. - auth1.signInWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_success_withPostBody() { - // Test successful sign in with popup where Auth event has POST body content. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - var recordedHandler = null; - var expectedEventId = '1234'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/callback', - 'SESSION_ID', - null, - 'POST_BODY'); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertNull(width); - assertNull(height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(tenantId); - assertEquals('POST_BODY', postBody); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser( - config3, expectedSamlTokenResponseWithIdPData, accountInfo); - var expectedPopupResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': expectedSamlAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.waitForSignals(5); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var provider = new fireauth.SAMLAuthProvider('saml.provider'); - // This should resolve with a null result. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Sign in with popup should resolve with expected result. - auth1.signInWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_success_slowIframeEmbed() { - // Test successful sign in with popup with delay in embedding the iframe. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - var recordedHandler = null; - var expectedEventId = '1234'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // User will be reloaded before sign in success. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function() { - // Additional delay in user reload should not trigger popup closed - // error. - clock.tick(timeoutDelay); - return goog.Promise.resolve(getAccountInfoResponse); - }); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - // Now that popup timer is cleared, a delay in verify assertion should - // not trigger popup closed error. - clock.tick(timeoutDelay); - // Resolve with expected token response. - return goog.Promise.resolve(expectedTokenResponseWithIdPData); - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // Simulate popup closed. - expectedPopup.closed = true; - // Simulate the iframe took a while to embed. This should not - // trigger a popup timeout. - clock.tick(timeoutDelay); - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - clock = new goog.testing.MockClock(true); - asyncTestCase.waitForSignals(4); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var provider = new fireauth.GoogleAuthProvider(); - // This should resolve with a null result. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Sign in with popup should not trigger popup closed error and should resolve - // successfully. - auth1.signInWithPopup(provider).then(function(popupResult) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - auth1.currentUser, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - popupResult); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_error_popupClosed() { - // Test when the popup is closed without completing sign in that the expected - // popup closed error is triggered. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - // Expected popup closed error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.POPUP_CLOSED_BY_USER); - var expectedEventId = '1234'; - // No Auth event detected, typically triggered when the iframe is ready and - // there is no event detected. - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // Simulate popup closed. - expectedPopup.closed = true; - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - onError(expectedError); - return goog.Promise.resolve(); - } - }; - }); - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var provider = new fireauth.GoogleAuthProvider(); - // This should resolve with a null result. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Sign in with popup should fail with the popup closed error. - auth1.signInWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_error_iframeWebStorageNotSupported() { - // Test when the web storage is not supported in the iframe. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - // Expected web storage not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - var expectedEventId = '1234'; - // Keep track when the popup is closed. - var isClosed = false; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Record when the popup is closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - isClosed = true; - assertEquals(expectedPopup, win); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'isWebStorageSupported': function() { - // Simulate web storage not supported in the iframe. - return goog.Promise.resolve(false); - }, - 'addAuthEventListener': function(handler) { - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - // Web storage not supported error. - onError(expectedError); - return goog.Promise.resolve(); - } - }; - }); - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var provider = new fireauth.GoogleAuthProvider(); - // This should resolve with a null result. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Sign in with popup should fail with the web storage no supported error. - auth1.signInWithPopup(provider).thenCatch(function(error) { - // Popup should be closed. - assertTrue(isClosed); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_success_cannotRunInBackground() { - // Test successful sign in with popup when tab cannot run in background. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - var recordedHandler = null; - var expectedEventId = '1234'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config3['authDomain'], - config3['apiKey'], - appId1, - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config3['authDomain'], domain); - assertEquals(config3['apiKey'], apiKey); - assertEquals(appId1, name); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - // simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.waitForSignals(4); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with popup should resolve with expected result. - auth1.signInWithPopup(expectedProvider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_success_cannotRunInBackground_tenantId() { - // Test successful sign-in with popup when tenant ID is passed and tab cannot - // run in background. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - var recordedHandler = null; - var expectedEventId = '1234'; - var expectedTenantId = 'TENANT_ID'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config3['authDomain'], - config3['apiKey'], - appId1, - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION, - null, - null, - expectedTenantId); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected, - actualTenantId) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - // Tenant ID should be passed to OAuth handler. - assertEquals(expectedTenantId, actualTenantId); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId, - clientVerison, additionalParams, endpointId, tenantId) { - assertEquals(config3['authDomain'], domain); - assertEquals(config3['apiKey'], apiKey); - assertEquals(appId1, name); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - assertEquals(expectedTenantId, tenantId); - return expectedUrl; - }); - // simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - var tenantUser = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfoWithTenantId); - var expectedPopupResult = { - 'user': tenantUser, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.waitForSignals(4); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set tenant Id on Auth. - auth1.tenantId = expectedTenantId; - // Sign in with popup should resolve with expected result. - auth1.signInWithPopup(expectedProvider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_success_iframeCanRunInBackground() { - // Test successful sign in with popup when tab can run in background but is an - // iframe. This should behave the same as the - // testAuth_signInWithPopup_success_cannotRunInBackground test. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - var recordedHandler = null; - var expectedEventId = '1234'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config3['authDomain'], - config3['apiKey'], - appId1, - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab can run in the background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - // Simulate app is running in an iframe. This should open the popup with the - // OAuth helper redirect directly. No additional redirect is needed as it - // could be blocked due to iframe sandboxing settings. - stubs.replace( - fireauth.util, - 'isIframe', - function() { - return true; - }); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Should already be redirected. - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config3['authDomain'], domain); - assertEquals(config3['apiKey'], apiKey); - assertEquals(appId1, name); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.waitForSignals(4); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with popup should resolve with expected result. - auth1.signInWithPopup(expectedProvider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_webStorageUnsupported_cantRunInBackground() { - // Test sign in with popup when the web storage is not supported in the iframe - // and the tab cannot run in background. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - // Keep track when the popup is closed. - var isClosed = false; - // Expected web storage not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - var expectedEventId = '1234'; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config3['authDomain'], - config3['apiKey'], - appId1, - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Check when the popup will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - isClosed = true; - assertEquals(expectedPopup, win); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'isWebStorageSupported': function() { - // Simulate web storage not supported in the iframe. - return goog.Promise.resolve(false); - }, - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - onError(expectedError); - return goog.Promise.resolve(); - } - }; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config3['authDomain'], domain); - assertEquals(config3['apiKey'], apiKey); - assertEquals(appId1, name); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with popup should reject with the expected error. - auth1.signInWithPopup(expectedProvider).thenCatch(function(error) { - assertTrue(isClosed); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_invalidEventId() { - // Test that a popup event belonging to a different owner does not resolve - // in the incorrect owner. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - var recordedHandler = null; - // Current owner's event ID. - var expectedEventId = '1234'; - // Sign in via popup triggered by another window with different ID. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - '5678', - 'http://www.example.com/#response', - 'SESSION_ID'); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return new goog.Promise(function(resolve, reject) {}); - } - }; - }); - // This should not run due to event ID mismatch. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectSignIn should not be called!'); - }); - asyncTestCase.waitForSignals(2); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var provider = new fireauth.GoogleAuthProvider(); - // This should resolve with a null result. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // This should not resolve as the event is owned by another tab. - auth1.signInWithPopup(provider).then(function(popupResult) { - fail('SignInWithPopup should not resolve due to event ID mismatch!'); - }); -} - - -function testAuth_signInWithPopup_error() { - // Test sign in with popup error. - fireauth.AuthEventManager.ENABLED = true; - var expectedPopup = { - 'close': function() {} - }; - var authEventHandler = null; - var expectedEventId = '1234'; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Sign in via popup with expected error. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - null, - null, - expectedError); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - authEventHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - authEventHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - // This should not resolve due to event error. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectSignIn should not be called due to error!'); - }); - asyncTestCase.waitForSignals(4); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var provider = new fireauth.GoogleAuthProvider(); - // This should resolve with a null result. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Sign in with popup should resolve with expected error. - auth1.signInWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_blockedPopup() { - // Test sign in with popup with blocked popup error. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.POPUP_BLOCKED); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return null; - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertNull(actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - return goog.Promise.reject(expectedError); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - return goog.Promise.resolve(); - } - }; - }); - asyncTestCase.waitForSignals(1); - var provider = new fireauth.GoogleAuthProvider(); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This should catch the blocked popup error. - auth1.signInWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_missingAuthDomain() { - // Test sign in with popup with missing Auth domain error. - fireauth.AuthEventManager.ENABLED = true; - // Fake popup. - var expectedPopup = { - 'close': function() {} - }; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.MISSING_AUTH_DOMAIN); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // Popup may try to close due to error if still open. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(3); - var provider = new fireauth.GoogleAuthProvider(); - // App initialized with no Auth domain. - app1 = firebase.initializeApp({ - 'apiKey': 'API_KEY' - }, appId1); - auth1 = app1.auth(); - // This should catch the blocked popup error - auth1.signInWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPopup_unsupportedEnvironment() { - // Test sign in with popup in unsupported environment. - // Simulate popup and redirect not supported in current environment. - stubs.replace( - fireauth.util, - 'isPopupRedirectSupported', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - asyncTestCase.waitForSignals(1); - var provider = new fireauth.GoogleAuthProvider(); - // App initialized correctly. - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This should catch the expected error. - auth1.signInWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithRedirect_unsupportedEnvironment() { - // Test sign in with redirect in unsupported environment. - // Simulate popup and redirect not supported in current environment. - stubs.replace( - fireauth.util, - 'isPopupRedirectSupported', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - asyncTestCase.waitForSignals(1); - var provider = new fireauth.GoogleAuthProvider(); - // App initialized correctly. - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This should catch the expected error. - auth1.signInWithRedirect(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_getRedirectResult_unsupportedEnvironment() { - // Test getRedirectResult in unsupported environment. - // Simulate popup and redirect not supported in current environment. - stubs.replace( - fireauth.util, - 'isPopupRedirectSupported', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - asyncTestCase.waitForSignals(3); - // App initialized correctly. - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This should catch the expected error. - auth1.getRedirectResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // State listener should fire once only with null user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertNull(currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with null. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNull(currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_signOut_clearSuccessRedirectResult() { - // Tests getRedirectResult with success event after signOut being called. - fireauth.AuthEventManager.ENABLED = true; - const expectedCred = fireauth.GoogleAuthProvider.credential( - null, 'ACCESS_TOKEN'); - // Expected sign in via redirect successful Auth event. - const expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - const manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - expectedPopupResult = { - 'user': user1, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - // User 1 should be set here and saved to storage. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return currentUserStorageManager.setCurrentUser(user1).then(() => { - return expectedPopupResult; - }); - }); - let user1, expectedPopupResult; - asyncTestCase.waitForSignals(3); - const pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - pendingRedirectManager.setPendingStatus().then(() => { - // Verify that the redirect result is resolved before signing out. - const manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - return manager.getRedirectResult().then((result) => { - // Expected result returned. - assertObjectEquals(expectedPopupResult, result); - return auth1.signOut(); - }).then(() => { - // signOut should clear the cached redirect result. - return auth1.getRedirectResult(); - }).then((resultAfterClearing) => { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_signOut_clearErrorRedirectResult() { - // Tests getRedirectResult with error event after signOut being called. - fireauth.AuthEventManager.ENABLED = true; - // The expected error. - const expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Expected sign in via redirect error Auth event. - const expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - null, - null, - expectedError); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - const manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectSignIn should not run on event error!'); - }); - asyncTestCase.waitForSignals(2); - const pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - pendingRedirectManager.setPendingStatus().then(() => { - // Verify that the redirect result is rejected with error before - // signing out. - const manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - return manager.getRedirectResult().thenCatch((error) => { - // Expected error returned. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - return auth1.signOut(); - }).then(() => { - // signOut should clear the error in cached redirect result. - return auth1.getRedirectResult(); - }).then((resultAfterClearing) => { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - }).thenCatch((error) => { - fail('Redirect result should be cleared by signOut.'); - }) ; -} - - -function testAuth_returnFromSignInWithRedirect_success_withoutPostBody() { - // Tests the return from a successful sign in with redirect. - fireauth.AuthEventManager.ENABLED = true; - var expectedCred = fireauth.GoogleAuthProvider.credential( - null, 'ACCESS_TOKEN'); - // Expected sign in via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - expectedPopupResult = { - 'user': user1, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - // User 1 should be set here and saved to storage. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return currentUserStorageManager.setCurrentUser(user1).then(function() { - return expectedPopupResult; - }); - }); - var user1, expectedPopupResult; - asyncTestCase.waitForSignals(5); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - pendingRedirectManager.setPendingStatus().then(function() { - // Get redirect result should resolve with the expected user and credential. - auth1.getRedirectResult().then(function(result) { - // Expected result returned. - assertObjectEquals(expectedPopupResult, result); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - }); - // State listener should fire once only with the final redirected user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with final redirected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertEquals(user1, currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_returnFromSignInWithRedirect_success_withPostBody() { - // Tests the return from a successful sign in with redirect where Auth event - // has POST body content. - fireauth.AuthEventManager.ENABLED = true; - // Expected sign in via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/callback', - 'SESSION_ID', - null, - 'POST_BODY'); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - user1 = new fireauth.AuthUser( - config3, expectedSamlTokenResponseWithIdPData, accountInfo); - expectedPopupResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': expectedSamlAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - // User 1 should be set here and saved to storage. - auth1.setCurrentUser_(user1); - asyncTestCase.signal(); - return currentUserStorageManager.setCurrentUser(user1).then(function() { - return expectedPopupResult; - }); - }); - var user1, expectedPopupResult; - asyncTestCase.waitForSignals(5); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - pendingRedirectManager.setPendingStatus().then(function() { - // Get redirect result should resolve with the expected user and credential. - auth1.getRedirectResult().then(function(result) { - // Expected result returned. - assertObjectEquals(expectedPopupResult, result); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - }); - // State listener should fire once only with the final redirected user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with final redirected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertEquals(user1, currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_returnFromSignInWithRedirect_success_tenantId() { - // Tests the return from a successful sign in with redirect where Auth event - // has tenant ID. - // Verify that if tenant ID is in the redirect Auth event, it will be passed - // to finishPopupAndRedirectSignIn handler and the redirect result with the - // tenant user will be returned. - fireauth.AuthEventManager.ENABLED = true; - var expectedTenantId = 'TENANT_ID'; - var expectedCred = fireauth.GoogleAuthProvider.credential( - null, 'ACCESS_TOKEN'); - // Expected sign in via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - tenantUser = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfoWithTenantId); - expectedRedirectResult = { - 'user': tenantUser, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - // Tenant user should be set here and saved to storage. - auth1.setCurrentUser_(tenantUser); - asyncTestCase.signal(); - return currentUserStorageManager.setCurrentUser(tenantUser) - .then(function() { - return expectedRedirectResult; - }); - }); - var tenantUser, expectedRedirectResult; - asyncTestCase.waitForSignals(5); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - pendingRedirectManager.setPendingStatus().then(function() { - // Get redirect result should resolve with the expected user and credential. - auth1.getRedirectResult().then(function(result) { - // Expected result returned. - assertObjectEquals(expectedRedirectResult, result); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - }); - // State listener should fire once only with the final redirected user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertEquals(tenantUser, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with final redirected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertEquals(tenantUser, currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_returnFromSignInWithRedirect_withExistingUser() { - // Tests the return from a successful sign in with redirect while having a - // previously signed in user. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - var expectedCred = fireauth.GoogleAuthProvider.credential(null, - 'ACCESS_TOKEN'); - // Expected sign in via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/#response', - 'SESSION_ID'); - initializeMockStorage(); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 and existing user2 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(auth1['currentUser'])); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - accountInfo['uid'] = '5678'; - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // New signed in user. - auth1.setCurrentUser_(user1); - // New user should have popup and redirect enabled. - user1.enablePopupRedirect(); - expectedPopupResult = { - 'user': user1, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.signal(); - // User 1 should be set here and saved to storage. - return currentUserStorageManager.setCurrentUser(user1).then(function() { - return expectedPopupResult; - }); - }); - var user1, expectedPopupResult; - accountInfo['uid'] = '1234'; - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - asyncTestCase.waitForSignals(5); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // When state is ready, currentUser if it exists should be resolved. - // In this we are simulating sign in with redirect when an existing - // user was already signed in. - currentUserStorageManager.setCurrentUser(user2).then(function() { - return pendingRedirectManager.setPendingStatus(); - }).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should return the new user and expected cred. - auth1.getRedirectResult().then(function(result) { - // Newly signed in user. - assertEquals(user1, auth1['currentUser']); - assertObjectEquals(expectedPopupResult, result); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - // State listener should fire once only with the final redirected user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with final redirected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertEquals(user1, currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_returnFromSignInWithRedirect_error() { - // Test return from sign in via redirect with error generated. - fireauth.AuthEventManager.ENABLED = true; - // The expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Expected sign in via redirect error Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - null, - null, - expectedError); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // As the event has an error this should not be called. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectSignIn should not run on event error!'); - }); - asyncTestCase.waitForSignals(4); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - pendingRedirectManager.setPendingStatus().then(function() { - // Get redirect result should return the expected error. - auth1.getRedirectResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Error in redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }).thenCatch(function(error) { - fail('Error in event should only be thrown once.'); - }); - }); - // State listener should fire once only with null user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertNull(currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with null user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNull(currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_returnFromSignInWithRedirect_error_webStorageNotSupported() { - // Test return from sign in via redirect with web storage not supported error. - fireauth.AuthEventManager.ENABLED = true; - // The expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - // Expected web storage not supported error Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - expectedError); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // As the event has an error this should not be called. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectSignIn should not run on event error!'); - }); - asyncTestCase.waitForSignals(3); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set pending redirect event. - pendingRedirectManager.setPendingStatus().then(function() { - // Get redirect result should return the expected error. - auth1.getRedirectResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Errors not being tied to a sign-in session should not be cleared. - return auth1.getRedirectResult(); - }).then(function(result) { - fail('Errors not being tied to a sign-in session should not be cleared.'); - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - }); - // State listener should fire once only with null user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertNull(currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with null user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNull(currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_returnFromSignInWithRedirect_noEvent() { - // Test get redirect result with no previous sign in via redirect attempt. - fireauth.AuthEventManager.ENABLED = true; - // No event detected. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected unknown event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // This should not run as there is no successful event. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectSignIn should not run on unknown event!'); - }); - asyncTestCase.waitForSignals(4); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - pendingRedirectManager.setPendingStatus().then(function() { - // Get redirect result should return null. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - }); - // State listener should fire once only with null user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertNull(currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with null user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNull(currentUser); - asyncTestCase.signal(); - }); -} - - -function testAuth_returnFromLinkWithRedirect_success_withoutPostBody() { - // Test link with redirect success. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - var expectedCred = fireauth.GoogleAuthProvider.credential(null, - 'ACCESS_TOKEN'); - stubs.reset(); - initializeMockStorage(); - // Expected link via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - // When state is ready, currentUser if it exists should be resolved. - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Assume user previously called link via redirect. - user1.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(user1); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 and user1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectLink. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - expectedPopupResult = { - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - asyncTestCase.waitForSignals(5); - var user1, expectedPopupResult; - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should resolve with expected user and credential. - auth1.getRedirectResult().then(function(result) { - assertObjectEquals(expectedPopupResult, result); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - // Should fire once only with the final redirected user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with expected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_returnFromLinkWithRedirect_success_withPostBody() { - // Test link with redirect success where Auth event has POST body content. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - stubs.reset(); - initializeMockStorage(); - // Expected link via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - 'http://www.example.com/callback', - 'SESSION_ID', - null, - 'POST_BODY'); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - // When state is ready, currentUser if it exists should be resolved. - user1 = new fireauth.AuthUser( - config3, expectedSamlTokenResponseWithIdPData, accountInfo); - // Assume user previously called link via redirect. - user1.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(user1); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 and user1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectLink. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - expectedPopupResult = { - 'user': this, - 'credential': null, - 'additionalUserInfo': expectedSamlAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - asyncTestCase.waitForSignals(5); - var user1, expectedPopupResult; - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should resolve with expected user and credential. - auth1.getRedirectResult().then(function(result) { - assertObjectEquals(expectedPopupResult, result); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - // Should fire once only with the final redirected user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with expected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_returnFromLinkWithRedirect_success_tenantId() { - // Test link with redirect success with tenant ID. - // Verify that if tenant ID is in the redirect Auth event, it will be passed - // to finishPopupAndRedirectLink handler and the redirect result with the - // tenant user will be returned. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - var expectedTenantId = 'TENANT_ID'; - var expectedCred = fireauth.GoogleAuthProvider.credential(null, - 'ACCESS_TOKEN'); - stubs.reset(); - initializeMockStorage(); - // Expected link via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - // When state is ready, currentUser if it exists should be resolved. - tenantUser = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfoWithTenantId); - // Assume user previously called link via redirect. - tenantUser.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(tenantUser); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 and user1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(tenantUser)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectLink. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - expectedRedirectResult = { - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - asyncTestCase.signal(); - return goog.Promise.resolve(expectedRedirectResult); - }); - asyncTestCase.waitForSignals(5); - var tenantUser, expectedRedirectResult; - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should resolve with expected user and credential. - auth1.getRedirectResult().then(function(result) { - assertObjectEquals(expectedRedirectResult, result); - asyncTestCase.signal(); - }); - // Should fire once only with the final redirected user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertUserEquals(tenantUser, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with expected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertUserEquals(tenantUser, currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_returnFromLinkWithRedirect_redirectedLoggedOutUser_success() { - // Test link with redirect success when a logged out user try to link with - // redirect. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - var expectedCred = fireauth.GoogleAuthProvider.credential(null, - 'ACCESS_TOKEN'); - // Expected link via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - stubs.reset(); - initializeMockStorage(); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - // When state is ready, currentUser if it exists should be resolved. - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - return goog.Promise.resolve(user1); - }); - // Simulate redirect user loaded from storage. - stubs.replace( - fireauth.storage.RedirectUserManager.prototype, - 'getRedirectUser', - function() { - // Create a logged out user that tried to link with redirect. - user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - user2.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(user2); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey) { - return { - 'addAuthEventListener': function(handler) { - // auth1, user1 and redirect user2 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - assertTrue(manager.isSubscribed(user2)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectLink. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - // This should be called on redirect user only. - assertEquals(user2, this); - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - expectedPopupResult = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - asyncTestCase.waitForSignals(5); - var user1, user2, expectedPopupResult; - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - // Get redirect result should resolve with redirect user (not current user) - // and expected credential. - auth1.getRedirectResult().then(function(result) { - // User1 logged in. - assertEquals(user1, auth1['currentUser']); - // Framework change should propagate to currentUser. - assertArrayEquals(['firebaseui'], auth1['currentUser'].getFramework()); - assertEquals('fr', auth1['currentUser'].getLanguageCode()); - // User2 expected in getRedirectResult. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user2, - // Expected credential returned. - expectedCred, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - result); - // Framework updates should still propagate to redirect user. - assertArrayEquals(['firebaseui'], result.user.getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], result.user.getFramework()); - // Language code should propagate to redirect user. - assertEquals('fr', result.user.getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', result.user.getLanguageCode()); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - // Should fire once only with the original user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with original user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_redirectedLoggedOutUser_differentAuthDomain() { - // Test link with redirect success when a logged out user with a different - // authDomain tries to link with redirect. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - // Simulate current origin is whitelisted. - simulateWhitelistedOrigin(); - initializeMockStorage(); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - var expectedEventId = '1234'; - var expectedCred = fireauth.GoogleAuthProvider.credential(null, - 'ACCESS_TOKEN'); - // Expected link via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey) { - return { - 'addAuthEventListener': function(handler) { - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectLink. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - return goog.Promise.resolve({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }); - }); - asyncTestCase.waitForSignals(3); - // The logged out user with the different authDomain. - var user1 = new fireauth.AuthUser( - config4, expectedTokenResponse, accountInfo); - user1.setRedirectEventId(expectedEventId); - // Auth will modify user to use its authDomain. - var modifiedUser1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - modifiedUser1.setRedirectEventId(expectedEventId); - // Set saved logged out redirect user using different authDomain. - redirectUserStorageManager = new fireauth.storage.RedirectUserManager( - config3['apiKey'] + ':' + appId1); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - redirectUserStorageManager.setRedirectUser(user1).then(function() { - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - // Get redirect result should resolve with redirect user and its - // authDomain overridden with the current app authDomain. - auth1.getRedirectResult().then(function(result) { - // No logged in user. - assertNull(auth1.currentUser); - // Redirect logged out user should have authDomain matching with - // app's. - assertUserEquals(modifiedUser1, result.user); - // Framework updates should still propagate to redirected user. - assertArrayEquals(['firebaseui'], result.user.getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], result.user.getFramework()); - // Language code should propagate to redirected user. - assertEquals('fr', result.user.getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', result.user.getLanguageCode()); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - // Should fire once only with null user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertNull(currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with null user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNull(currentUser); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testAuth_returnFromLinkWithRedirect_noCurrentUser_redirectUser() { - // Test link with redirect success when a logged out user try to link with - // redirect. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - var expectedCred = fireauth.GoogleAuthProvider.credential(null, - 'ACCESS_TOKEN'); - // Expected link via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - stubs.reset(); - initializeMockStorage(); - // Simulate redirect user loaded from storage. - stubs.replace( - fireauth.storage.RedirectUserManager.prototype, - 'getRedirectUser', - function() { - // Create a logged out user that tried to link with redirect. - user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - user2.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(user2); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey) { - return { - 'addAuthEventListener': function(handler) { - // auth1, redirect user2 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user2)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectLink. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - // This should be called on redirect user only. - assertEquals(user2, this); - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - expectedPopupResult = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - asyncTestCase.waitForSignals(5); - var user2, expectedPopupResult; - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set language code. - auth1.languageCode = 'fr'; - // Log framework. - auth1.logFramework('firebaseui'); - pendingRedirectManager.setPendingStatus().then(function() { - // Get redirect result should resolve with redirect user (not current user) - // and expected credential. - auth1.getRedirectResult().then(function(result) { - // No current user. - assertNull(auth1['currentUser']); - // User2 expected in getRedirectResult. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user2, - // Expected credential returned. - expectedCred, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - result); - // Framework updates should still propagate to redirected non current - // user. - assertArrayEquals(['firebaseui'], result.user.getFramework()); - auth1.logFramework('angularfire'); - assertArrayEquals( - ['firebaseui', 'angularfire'], result.user.getFramework()); - // Language code should propagate to redirected non current user. - assertEquals('fr', result.user.getLanguageCode()); - auth1.languageCode = 'de'; - assertEquals('de', result.user.getLanguageCode()); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - // Should fire once only with null user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertNull(currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with null user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNull(currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_returnFromLinkWithRedirect_noUsers() { - // Test link with redirect success when a logged out user try to link with - // redirect. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - // Expected link via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectLink. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectLink should not call!'); - }); - asyncTestCase.waitForSignals(4); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should resolve with redirect user (not current user) - // and expected credential. - auth1.getRedirectResult().then(function(result) { - // No current user. - assertNull(auth1['currentUser']); - // No results. - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Should fire once only with null user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertNull(currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with null user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNull(currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_returnFromLinkWithRedirect_redirectedLoggedInUser_success() { - // Test link with redirect success when a logged in user tries to redirect. - // The same user will typically be also retrieved from storage but only the - // current user will be handled. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - var expectedCred = fireauth.GoogleAuthProvider.credential(null, - 'ACCESS_TOKEN'); - // Expected link via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - stubs.reset(); - initializeMockStorage(); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - // When state is ready, currentUser if it exists should be resolved. - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Current user user1 tried to link via redirect. - user1.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(user1); - }); - // Simulate redirect user loaded from storage. - stubs.replace( - fireauth.storage.RedirectUserManager.prototype, - 'getRedirectUser', - function() { - // The same user should be save as rediret user too. - user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - user2.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(user2); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey) { - return { - 'addAuthEventListener': function(handler) { - // auth1, user1 and redirect user2 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - assertTrue(manager.isSubscribed(user2)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectLink. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - // This should be called on current user only as he is subscribed first. - assertEquals(user1, this); - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - expectedPopupResult = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedPopupResult); - }); - asyncTestCase.waitForSignals(5); - var user1, user2, expectedPopupResult; - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should resolve with redirect user (not current user) - // and expected credential. - auth1.getRedirectResult().then(function(result) { - // User1 logged in. - assertEquals(user1, auth1['currentUser']); - // User1 expected in getRedirectResult. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user1, - // Expected credential returned. - expectedCred, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.SIGN_IN, - result); - // Redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }); - // Should fire once only with redirected user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with redirected user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_returnFromLinkWithRedirect_invalidUser() { - // Test link with redirect event belonging to a diffent user. - // This could happen if the same user started the redirect event in a - // different tab. The event should resolve in the same tab. - fireauth.AuthEventManager.ENABLED = true; - var expectedEventId = '1234'; - // Successful link via redirect belonging to another user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - 'OTHER_ID', - 'http://www.example.com/#response', - 'SESSION_ID'); - stubs.reset(); - initializeMockStorage(); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - // When state is ready, currentUser if it exists should be resolved. - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Set redirect event ID. - user1.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(user1); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 and user1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - // At this stage user and Auth are subscribed. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // This should not run as the logged in user's event ID does not match with - // detected event ID. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectLink should not call due to UID mismatch!'); - }); - asyncTestCase.waitForSignals(4); - var user1; - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Due to event ID mismatch, this should return null. - auth1.getRedirectResult().then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - asyncTestCase.signal(); - }); - // Should fire once only with original user1. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with original user1. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_returnFromLinkWithRedirect_error() { - // Test return from link with redirect event having an error. - fireauth.AuthEventManager.ENABLED = true; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var expectedEventId = '1234'; - // Expected link via redirect event with error. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - expectedEventId, - null, - null, - expectedError); - stubs.reset(); - initializeMockStorage(); - // Simulate user loaded from storage. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - fireauth.storage.UserManager.prototype, - 'getCurrentUser', - function() { - // When state is ready, currentUser if it exists should be resolved. - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Set user as owner of this event. - user1.setRedirectEventId(expectedEventId); - return goog.Promise.resolve(user1); - }); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 and user1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - assertTrue(manager.isSubscribed(user1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // As the event contains an error, this should not run. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectLink should not call due to event error!'); - }); - asyncTestCase.waitForSignals(4); - var user1; - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should contain an error. - auth1.getRedirectResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Error in redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(resultAfterClearing) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, resultAfterClearing); - asyncTestCase.signal(); - }).thenCatch(function(error) { - fail('Error in event should only be thrown once.'); - }); - // Should fire once only with original user1. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with original user1. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertUserEquals(user1, currentUser); - asyncTestCase.signal(); - }); - }); -} - - -function testAuth_signInWithRedirect_success_unloadsOnRedirect() { - // Test successful request for sign in via redirect when page unloads on - // redirect. - fireauth.AuthEventManager.ENABLED = true; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedMode = fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT; - asyncTestCase.waitForSignals(1); - // Track calls to savePersistenceForRedirect. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'savePersistenceForRedirect', - goog.testing.recordFunction()); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'unloadsOnRedirect': function() { return true; }, - 'processRedirect': function( - actualMode, actualProvider, actualEventId) { - assertEquals(expectedMode, actualMode); - assertEquals(expectedProvider, actualProvider); - assertUndefined(actualEventId); - // Confirm current persistence is saved before redirect. - assertEquals( - 1, - fireauth.storage.UserManager.prototype - .savePersistenceForRedirect.getCallCount()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - } - }; - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // sign in with redirect should call processRedirect underneath and remain - // pending. - auth1.signInWithRedirect(expectedProvider).then(function() { - fail('SignInWithRedirect should remain pending in environment where ' + - 'OAuthSignInHandler unloads the page.'); - }); -} - - -function testAuth_signInWithRedirect_success_unloadsOnRedirect_tenantId() { - // Test successful request for sign in via redirect when page unloads on - // redirect and tenant ID is passed. - // Verify that tenant ID is passed to OAuth sign in handler. - fireauth.AuthEventManager.ENABLED = true; - var expectedTenantId = 'TENANT_ID'; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedMode = fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT; - asyncTestCase.waitForSignals(1); - // Track calls to savePersistenceForRedirect. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'savePersistenceForRedirect', - goog.testing.recordFunction()); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'unloadsOnRedirect': function() { return true; }, - 'processRedirect': function( - actualMode, actualProvider, actualEventId, actualTenantId) { - assertEquals(expectedMode, actualMode); - assertEquals(expectedProvider, actualProvider); - assertUndefined(actualEventId); - // Tenant ID should be passed to OAuth handler. - assertEquals(expectedTenantId, actualTenantId); - // Confirm current persistence is saved before redirect. - assertEquals( - 1, - fireauth.storage.UserManager.prototype - .savePersistenceForRedirect.getCallCount()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - } - }; - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set tenant ID on Auth. - auth1.tenantId = expectedTenantId; - // sign in with redirect should call processRedirect underneath and remain - // pending. - auth1.signInWithRedirect(expectedProvider).then(function() { - fail('SignInWithRedirect should remain pending in environment where ' + - 'OAuthSignInHandler unloads the page.'); - }); -} - - -function testAuth_signInWithRedirect_success_doesNotUnloadOnRedirect() { - // Test successful request for sign in via redirect when page does not unload - // on redirect. - fireauth.AuthEventManager.ENABLED = true; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedMode = fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT; - asyncTestCase.waitForSignals(1); - // Track calls to savePersistenceForRedirect. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'savePersistenceForRedirect', - goog.testing.recordFunction()); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'unloadsOnRedirect': function() { return false; }, - 'processRedirect': function( - actualMode, actualProvider, actualEventId) { - assertEquals(expectedMode, actualMode); - assertEquals(expectedProvider, actualProvider); - assertUndefined(actualEventId); - // Confirm current persistence is saved before redirect. - assertEquals( - 1, - fireauth.storage.UserManager.prototype - .savePersistenceForRedirect.getCallCount()); - return goog.Promise.resolve(); - } - }; - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with redirect should resolve in this case as the page does not - // necessarily unload. - auth1.signInWithRedirect(expectedProvider).then(function() { - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithRedirect_success_doesNotUnload_tenantId() { - // Test successful request for sign in via redirect when page does not unload - // on redirect and tenant ID is passed. - fireauth.AuthEventManager.ENABLED = true; - var expectedTenantId = 'TENANT_ID'; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedMode = fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT; - asyncTestCase.waitForSignals(1); - // Track calls to savePersistenceForRedirect. - stubs.replace( - fireauth.storage.UserManager.prototype, - 'savePersistenceForRedirect', - goog.testing.recordFunction()); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'unloadsOnRedirect': function() { return false; }, - 'processRedirect': function( - actualMode, actualProvider, actualEventId, actualTenantId) { - assertEquals(expectedMode, actualMode); - assertEquals(expectedProvider, actualProvider); - assertUndefined(actualEventId); - // Tenant ID should be passed to OAuth handler. - assertEquals(expectedTenantId, actualTenantId); - // Confirm current persistence is saved before redirect. - assertEquals( - 1, - fireauth.storage.UserManager.prototype - .savePersistenceForRedirect.getCallCount()); - return goog.Promise.resolve(); - } - }; - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Set tenant ID on Auth. - auth1.tenantId = expectedTenantId; - // Sign in with redirect should resolve in this case as the page does not - // necessarily unload. - auth1.signInWithRedirect(expectedProvider).then(function() { - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithRedirect_missingAuthDomain() { - // Test failing request for sign in via redirect due to missing Auth domain. - fireauth.AuthEventManager.ENABLED = true; - // No Auth domain supplied. - var config = { - 'apiKey': 'API_KEY' - }; - // Expected missing Auth domain error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.MISSING_AUTH_DOMAIN); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config, appId1); - auth1 = app1.auth(); - var provider = new fireauth.GoogleAuthProvider(); - // Sign in with redirect should fail with missing Auth domain. - auth1.signInWithRedirect(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithRedirect_invalidProvider() { - // Test sign in with redirect failing with invalid provider. - fireauth.AuthEventManager.ENABLED = true; - // Expected invalid OAuth provider error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - asyncTestCase.waitForSignals(1); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'unloadsOnRedirect': function() { return true; }, - 'processRedirect': function( - actualMode, actualProvider, actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, actualMode); - assertEquals(provider, actualProvider); - assertUndefined(actualEventId); - return goog.Promise.reject(expectedError); - } - }; - }); - var provider = new fireauth.EmailAuthProvider(); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Sign in with redirect should fail with invalid OAuth provider error. - auth1.signInWithRedirect(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_returnFromSignInWithRedirect_timeout() { - // Test return from sign in with redirect getRedirectResult timing out. - fireauth.AuthEventManager.ENABLED = true; - // Expected timeout error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TIMEOUT); - clock = new goog.testing.MockClock(true); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 and user1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // This should not run due to timeout error. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectSignIn should not call due to timeout!'); - }); - asyncTestCase.waitForSignals(4); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should fail with timeout error. - auth1.getRedirectResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // Should fire once only with null user. - var idTokenChangeCounter = 0; - auth1.onIdTokenChanged(function(currentUser) { - idTokenChangeCounter++; - assertEquals(1, idTokenChangeCounter); - assertNull(currentUser); - asyncTestCase.signal(); - }); - var userChanges = 0; - // Should be called with null user. - auth1.onAuthStateChanged(function(currentUser) { - userChanges++; - assertEquals(1, userChanges); - assertNull(currentUser); - asyncTestCase.signal(); - }); - // Speed up timeout. - clock.tick(timeoutDelay); - }); -} - - -function testAuth_invalidateSession_tokenExpired() { - // Test when a token expired error is triggered on a current user that the - // user is signed out. - fireauth.AuthEventManager.ENABLED = true; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Expected token error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - // Whether to trigger the token error or not. - var triggerTokenError = false; - // Stub token manager to either throw the token error or the valid tokens. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - if (triggerTokenError) { - return goog.Promise.reject(expectedError); - } - return goog.Promise.resolve({ - 'accessToken': jwt1, - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Access token unchanged, should trigger notifyAuthListeners_. - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Save current user in storage. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - - // Track token change events. - var tokenChangeCounter = 0; - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This should trigger initially and then on sign out. - auth1.addAuthTokenListener(function(token) { - // Keep track. - tokenChangeCounter++; - if (token) { - // Initial sign in. - assertEquals(1, tokenChangeCounter); - // Confirm ID token. - assertEquals(jwt1, token); - // Force token error on next call. - triggerTokenError = true; - // Token error should be detected. - auth1.currentUser.getIdToken(true).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); - } else { - // Should be triggered again on user invalidation. - assertEquals(2, tokenChangeCounter); - // User signed out. - assertNull(auth1.currentUser); - // User cleared from storage. - currentUserStorageManager.getCurrentUser().then(function(user) { - // No user stored anymore. - assertNull(user); - asyncTestCase.signal(); - }); - } - }); - }); -} - - -function testAuth_invalidateSession_userDisabled() { - // Test when a user disabled error is triggered on a current user that the - // user is signed out. - fireauth.AuthEventManager.ENABLED = true; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Expected token error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - // Whether to trigger the token error or not. - var triggerTokenError = false; - // Stub token manager to either throw the token error or the valid tokens. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - if (triggerTokenError) { - return goog.Promise.reject(expectedError); - } - return goog.Promise.resolve({ - 'accessToken': jwt1, - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Access token unchanged, should trigger notifyAuthListeners_. - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Save current user in storage. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - - // Track token change events. - var tokenChangeCounter = 0; - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This should trigger initially and then on sign out. - auth1.addAuthTokenListener(function(token) { - // Keep track. - tokenChangeCounter++; - if (token) { - // Initial sign in. - assertEquals(1, tokenChangeCounter); - // Confirm ID token. - assertEquals(jwt1, token); - // Force token error on next call. - triggerTokenError = true; - // Token error should be detected. - auth1.currentUser.getIdToken(true).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); - } else { - // Should be triggered again on user invalidation. - assertEquals(2, tokenChangeCounter); - // User signed out. - assertNull(auth1.currentUser); - // User cleared from storage. - currentUserStorageManager.getCurrentUser().then(function(user) { - // No user stored anymore. - assertNull(user); - asyncTestCase.signal(); - }); - } - }); - }); -} - - -function testAuth_invalidateSession_dispatchUserInvalidatedEvent() { - // Test when a user invalidate event is dispatched on a current user that the - // user is signed out. - fireauth.AuthEventManager.ENABLED = true; - // Stub OAuth sign in handler. - fakeOAuthSignInHandler(); - // Stub token manager to return valid tokens. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function() { - return goog.Promise.resolve({ - 'accessToken': jwt1, - 'refreshToken': 'REFRESH_TOKEN' - }); - }); - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - // Access token unchanged, should trigger notifyAuthListeners_. - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Save current user in storage. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - - // Track token change events. - var tokenChangeCounter = 0; - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // This should trigger initially and then on sign out. - auth1.addAuthTokenListener(function(token) { - // Keep track. - tokenChangeCounter++; - if (token) { - // Initial sign in. - assertEquals(1, tokenChangeCounter); - // Confirm ID token. - assertEquals(jwt1, token); - // Dispatch user invalidation event on current user. - auth1.currentUser.dispatchEvent( - fireauth.UserEventType.USER_INVALIDATED); - } else { - // Should be triggered again on user invalidation. - assertEquals(2, tokenChangeCounter); - // User signed out. - assertNull(auth1.currentUser); - // User cleared from storage. - currentUserStorageManager.getCurrentUser().then(function(user) { - // No user stored anymore. - assertNull(user); - asyncTestCase.signal(); - }); - } - }); - }); -} - - -function testAuth_proactiveTokenRefresh_multipleUsers() { - // Test proactive refresh when a Firebase service is added before sign in - // and multiple users are signed in successively. - // Record startProactiveRefresh and stopProactiveRefresh calls. - asyncTestCase.waitForSignals(1); - stubs.replace( - fireauth.AuthUser.prototype, - 'startProactiveRefresh', - goog.testing.recordFunction()); - stubs.replace( - fireauth.AuthUser.prototype, - 'stopProactiveRefresh', - goog.testing.recordFunction()); - // Logged in users. - var accountInfo1 = { - 'uid': '1234', - 'email': 'user1@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - var accountInfo2 = { - 'uid': '5678', - 'email': 'user2@example.com', - 'displayName': 'John Smith', - 'emailVerified': false - }; - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo1); - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo2); - var calls = 0; - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - calls++; - // Return user1 on first call and user2 on second. - if (calls == 1) { - return goog.Promise.resolve(user1); - } else { - return goog.Promise.resolve(user2); - } - }); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var subscriber = function(token) {}; - // Simulate Firebase service added. - app1.container.getProvider('auth-internal').getImmediate().addAuthTokenListener(subscriber); - // Simulate user1 signed in. - auth1.signInWithIdTokenResponse(expectedTokenResponse).then(function() { - // Current user should be set to user1. - assertEquals(user1, auth1['currentUser']); - // Confirm proactive refresh started on user1. - assertEquals( - 1, fireauth.AuthUser.prototype.startProactiveRefresh.getCallCount()); - assertEquals( - user1, - fireauth.AuthUser.prototype.startProactiveRefresh.getLastCall() - .getThis()); - // Stop not yet called. - assertEquals( - 0, fireauth.AuthUser.prototype.stopProactiveRefresh.getCallCount()); - // Sign in another user. - return auth1.signInWithIdTokenResponse(expectedTokenResponse); - }).then(function() { - // Current user should be set to user2. - assertEquals(user2, auth1['currentUser']); - // Confirm proactive refresh started on user2. - assertEquals( - 2, fireauth.AuthUser.prototype.startProactiveRefresh.getCallCount()); - assertEquals( - user2, - fireauth.AuthUser.prototype.startProactiveRefresh.getLastCall() - .getThis()); - // Stop proactive refresh on user1. - assertEquals( - 1, fireauth.AuthUser.prototype.stopProactiveRefresh.getCallCount()); - assertEquals( - user1, - fireauth.AuthUser.prototype.stopProactiveRefresh.getLastCall() - .getThis()); - // Sign out the user2. - return auth1.signOut(); - }).then(function() { - // Confirm proactive refresh stopped on user2. - assertEquals( - 2, fireauth.AuthUser.prototype.stopProactiveRefresh.getCallCount()); - assertEquals( - user2, - fireauth.AuthUser.prototype.stopProactiveRefresh.getLastCall() - .getThis()); - asyncTestCase.signal(); - }); -} - - -function testAuth_proactiveTokenRefresh_firebaseServiceAddedAfterSignIn() { - // Test proactive refresh when a Firebase service is added after sign in. - // Record startProactiveRefresh and stopProactiveRefresh calls. - asyncTestCase.waitForSignals(1); - stubs.replace( - fireauth.AuthUser.prototype, - 'startProactiveRefresh', - goog.testing.recordFunction()); - stubs.replace( - fireauth.AuthUser.prototype, - 'stopProactiveRefresh', - goog.testing.recordFunction()); - var subscriber = function(token) {}; - // Logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - // Save current user in storage. - currentUserStorageManager = new fireauth.storage.UserManager( - config3['apiKey'] + ':' + appId1); - // Simulate user signed in. - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var unsubscribe = auth1.onIdTokenChanged(function(user) { - unsubscribe(); - // Confirm proactive refresh not started on that user. - assertEquals( - 0, fireauth.AuthUser.prototype.startProactiveRefresh.getCallCount()); - // Simulate Firebase service added. - app1.container.getProvider('auth-internal').getImmediate().addAuthTokenListener(subscriber); - // Confirm proactive refresh started on that user. - assertEquals( - 1, fireauth.AuthUser.prototype.startProactiveRefresh.getCallCount()); - // Confirm proactive refresh not stopped yet on that user. - assertEquals( - 0, fireauth.AuthUser.prototype.stopProactiveRefresh.getCallCount()); - // Sign out the user. - auth1.signOut().then(function() { - // Confirm proactive refresh stopped on that user. - assertEquals( - 1, fireauth.AuthUser.prototype.stopProactiveRefresh.getCallCount()); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testAuth_proactiveTokenRefresh_firebaseServiceRemovedAfterSignIn() { - // Test proactive refresh stopped when a Firebase service is removed. - // Record startProactiveRefresh and stopProactiveRefresh calls. - asyncTestCase.waitForSignals(1); - stubs.replace( - fireauth.AuthUser.prototype, - 'startProactiveRefresh', - goog.testing.recordFunction()); - stubs.replace( - fireauth.AuthUser.prototype, - 'stopProactiveRefresh', - goog.testing.recordFunction()); - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - return goog.Promise.resolve(user1); - }); - // Logged in user. - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var subscriber = function(token) {}; - // Simulate Firebase service added. - authInternal1 = app1.container.getProvider('auth-internal').getImmediate(); - authInternal1.addAuthTokenListener(subscriber); - // Add same listener again to check that removing it once will ensure the - // proactive refresh is stopped. - authInternal1.addAuthTokenListener(subscriber); - // Simulate user signed in. - auth1.signInWithIdTokenResponse(expectedTokenResponse).then(function() { - // Current user should be set to user1. - assertEquals(user1, auth1['currentUser']); - // Confirm proactive refresh started on that user. - assertEquals( - 1, fireauth.AuthUser.prototype.startProactiveRefresh.getCallCount()); - // Stop not yet called. - assertEquals( - 0, fireauth.AuthUser.prototype.stopProactiveRefresh.getCallCount()); - // Simulate Firebase service removed. - authInternal1.removeAuthTokenListener(subscriber); - // Confirm proactive refresh stopped on that user. - assertEquals( - 1, fireauth.AuthUser.prototype.stopProactiveRefresh.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPhoneNumber_success() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var expectedVerificationId = 'VERIFICATION_ID'; - var expectedCode = '123456'; - var expectedPhoneNumber = '+15551234567'; - var expectedRecaptchaToken = 'RECAPTCHA_TOKEN'; - var expectedCredential = fireauth.PhoneAuthProvider.credential( - expectedVerificationId, expectedCode); - var appVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(expectedRecaptchaToken); - } - }; - // Expected promise to be returned by signInWithCredential. - var expectedPromise = new goog.Promise(function(resolve, reject) {}); - // Phone Auth provider instance. - var phoneAuthProviderInstance = - mockControl.createStrictMock(fireauth.PhoneAuthProvider); - // Phone Auth provider constructor mock. - var phoneAuthProviderConstructor = mockControl.createConstructorMock( - fireauth, 'PhoneAuthProvider'); - // Provider instance should be initialized with the expected Auth instance - // and return the expected phone Auth provider instance. - phoneAuthProviderConstructor(auth1) - .$returns(phoneAuthProviderInstance).$once(); - // verifyPhoneNumber called on provider instance with the expected phone - // number and appVerifier. This would resolve with the expected verification - // ID. - phoneAuthProviderInstance.verifyPhoneNumber( - expectedPhoneNumber, appVerifier) - .$returns(goog.Promise.resolve(expectedVerificationId)).$once(); - // Code confirmation should call signInWithCredential with the expected - // credential. - stubs.replace( - fireauth.Auth.prototype, - 'signInWithCredential', - goog.testing.recordFunction(function(cred) { - // Confirm expected credential passed. - assertObjectEquals( - expectedCredential.toPlainObject(), - cred.toPlainObject()); - // Return expected promise. - return expectedPromise; - })); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - // Call signInWithPhoneNumber. - auth1.signInWithPhoneNumber(expectedPhoneNumber, appVerifier) - .then(function(confirmationResult) { - // Confirmation result returned should contain expected verification ID. - assertEquals( - expectedVerificationId, confirmationResult['verificationId']); - // Code confirmation should return the same response as the underlying - // signInAndRetrieveDataWithCredential. - assertEquals(expectedPromise, confirmationResult.confirm(expectedCode)); - // Confirm signInAndRetrieveDataWithCredential called once. - assertEquals( - 1, - fireauth.Auth.prototype.signInWithCredential.getCallCount()); - // Confirm signInAndRetrieveDataWithCredential is bound to auth1. - assertEquals( - auth1, - fireauth.Auth.prototype.signInWithCredential - .getLastCall().getThis()); - asyncTestCase.signal(); - }); -} - - -function testAuth_signInWithPhoneNumber_error() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var expectedPhoneNumber = '+15551234567'; - var expectedRecaptchaToken = 'RECAPTCHA_TOKEN'; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var appVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(expectedRecaptchaToken); - } - }; - // Phone Auth provider instance. - var phoneAuthProviderInstance = - mockControl.createStrictMock(fireauth.PhoneAuthProvider); - // Phone Auth provider constructor mock. - var phoneAuthProviderConstructor = mockControl.createConstructorMock( - fireauth, 'PhoneAuthProvider'); - // Mock signInAndRetrieveDataWithCredential. - var signInAndRetrieveDataWithCredential = mockControl.createMethodMock( - fireauth.Auth.prototype, 'signInAndRetrieveDataWithCredential'); - // Provider instance should be initialized with the expected Auth instance - // and return the expected phone Auth provider instance. - phoneAuthProviderConstructor(auth1) - .$returns(phoneAuthProviderInstance).$once(); - // verifyPhoneNumber called on provider instance with the expected phone - // number and appVerifier. This would reject with the expected error. - phoneAuthProviderInstance.verifyPhoneNumber( - expectedPhoneNumber, appVerifier) - .$returns(goog.Promise.reject(expectedError)).$once(); - // signInAndRetrieveDataWithCredential should not be called. - signInAndRetrieveDataWithCredential(ignoreArgument).$times(0); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - // Call signInWithPhoneNumber. - auth1.signInWithPhoneNumber(expectedPhoneNumber, appVerifier) - .thenCatch(function(error) { - // This should throw the same error thrown by verifyPhoneNumber. - assertEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testAuth_setPersistence_invalid() { - var unsupportedTypeError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_PERSISTENCE); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Session should throw an unsupported persistence type error. - fireauth.common.testHelper.assertErrorEquals( - unsupportedTypeError, - assertThrows(function() { - auth1.setPersistence('bla'); - })); -} - - -function testAuth_setPersistence_noExistingAuthState() { - // Test when persistence is set that future sign-in attempts are stored - // using specified persistence. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - calls++; - // Return user1 on first call and user2 on second. - if (calls == 1) { - assertObjectEquals(expectedTokenResponse, idTokenResponse); - return goog.Promise.resolve(user1); - } else { - assertObjectEquals(expectedTokenResponse2, idTokenResponse); - return goog.Promise.resolve(user2); - } - }); - // Stub verifyPassword to return tokens for first user. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyPassword', - function(email, password) { - return goog.Promise.resolve(expectedTokenResponse); - }); - // Stub verifyCustomToken to return tokens for second user. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyCustomToken', - function(customToken) { - return goog.Promise.resolve(expectedTokenResponse2); - }); - // Fixes weird IE flakiness. - clock = new goog.testing.MockClock(true); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, {'uid': '1234'}); - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse2, {'uid': '5678'}); - var calls = 0; - asyncTestCase.waitForSignals(1); - // Switch to session persistence. - auth1.setPersistence('session'); - clock.tick(1); - // Sign in with email/password. - auth1.signInWithEmailAndPassword( - 'user@example.com', 'password').then(function(userCredential) { - clock.tick(1); - // Confirm first user saved in session storage. - assertUserEquals(user1, userCredential['user']); - return fireauth.common.testHelper.assertUserStorage( - auth1.getStorageKey(), 'session', user1, - fireauth.authStorage.Manager.getInstance()); - }).then(function() { - // Sign in with custom token. - return auth1.signInWithCustomToken('CUSTOM_TOKEN'); - }).then(function() { - // Confirm second user saved in session storage. - clock.tick(1); - return fireauth.common.testHelper.assertUserStorage( - auth1.getStorageKey(), 'session', user2, - fireauth.authStorage.Manager.getInstance()); - }).then(function() { - asyncTestCase.signal(); - }); -} - - -function testAuth_setPersistence_existingAuthState() { - // Test when persistence is set after initialization, a stored Auth state - // will be switched to the new type of storage. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - return goog.Promise.resolve(user2); - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'signInAnonymously', - function() { - // Return tokens for second test user. - return goog.Promise.resolve(expectedTokenResponse2); - }); - asyncTestCase.waitForSignals(2); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, {'uid': '1234'}); - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse2, {'uid': '5678'}); - currentUserStorageManager = - new fireauth.storage.UserManager(config3['apiKey'] + ':' + appId1); - // Simulate logged in user, save to storage, it will be picked up on init - // Auth state. This will use the default local persistence. - currentUserStorageManager.setCurrentUser(user1).then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Switch to session persistence. - auth1.setPersistence('session'); - var unsubscribe = auth1.onAuthStateChanged(function(user) { - unsubscribe(); - // When this is first triggered, the previously signed in user should be - // switched to session storage. - fireauth.common.testHelper.assertUserStorage( - config3['apiKey'] + ':' + appId1, 'session', user1, - fireauth.authStorage.Manager.getInstance()).then(function() { - // Sign in a new user. - return auth1.signInAnonymously(); - }).then(function() { - // Second user should be also persistence in session storage. - return fireauth.common.testHelper.assertUserStorage( - config3['apiKey'] + ':' + appId1, 'session', user2, - fireauth.authStorage.Manager.getInstance()); - }).then(function() { - asyncTestCase.signal(); - }); - }); - // Track all token changes. Confirm persistence changes do not trigger - // unexpected calls. - var uidsDetected = []; - auth1.onIdTokenChanged(function(user) { - // Keep track of UIDs each time this is called. - uidsDetected.push(user && user.uid); - if (uidsDetected.length == 2) { - assertArrayEquals(['1234', '5678'], uidsDetected); - asyncTestCase.signal(); - } else if (uidsDetected.length > 2) { - fail('Unexpected token change!'); - } - }); - }); -} - - -function testAuth_temporaryPersistence_externalChange() { - // Test when temporary persistence is set and an external change is detected - // local persistence is set after synchronization. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - return goog.Promise.resolve(user3); - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'signInAnonymously', - function() { - // Return tokens for third test user. - return goog.Promise.resolve(expectedTokenResponse3); - }); - var storageKey = 'firebase:authUser:' + config3['apiKey'] + ':' + appId1; - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, {'uid': '1234'}); - var user2 = new fireauth.AuthUser( - config3, expectedTokenResponse2, {'uid': '5678'}); - var user3 = new fireauth.AuthUser( - config3, expectedTokenResponse3, {'uid': '9012'}); - var storageEvent = new goog.testing.events.Event( - goog.events.EventType.STORAGE, window); - // Simulate existing user stored in session storage. - mockSessionStorage.set(storageKey, user1.toPlainObject()); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - storageEvent.key = 'firebase:authUser:' + auth1.getStorageKey(); - storageEvent.oldValue = null; - storageEvent.newValue = JSON.stringify(user2.toPlainObject()); - asyncTestCase.waitForSignals(1); - var calls = 0; - auth1.onIdTokenChanged(function(user) { - calls++; - if (calls == 1) { - // On first call, the first user should be stored in session storage. - assertUserEquals(user1, user); - fireauth.common.testHelper.assertUserStorage( - auth1.getStorageKey(), 'session', user1, - fireauth.authStorage.Manager.getInstance()).then(function() { - // Simulate external user signed in on another tab. - mockLocalStorage.set( - storageKey, user2.toPlainObject()); - mockLocalStorage.fireBrowserEvent(storageEvent); - }); - } else if (calls == 2) { - // On second call, the second user detected from external event should - // be detected and stored in local storage. - assertUserEquals(user2, user); - fireauth.common.testHelper.assertUserStorage( - auth1.getStorageKey(), 'local', user2, - fireauth.authStorage.Manager.getInstance()).then(function() { - // Sign in anonymously. - auth1.signInAnonymously(); - }); - } else if (calls == 3) { - // Third anonymous user detected and should be stored in local storage. - assertUserEquals(user3, user); - fireauth.common.testHelper.assertUserStorage( - auth1.getStorageKey(), 'local', user3, - fireauth.authStorage.Manager.getInstance()).then(function() { - asyncTestCase.signal(); - }); - } - }); -} - - -function testAuth_storedPersistence_returnFromRedirect() { - // getRedirectResult will resolve with the user stored with expected - // persistence from the previous page. - // Tests the return from a successful sign in with redirect. - fireauth.AuthEventManager.ENABLED = true; - var expectedCred = fireauth.GoogleAuthProvider.credential( - null, 'ACCESS_TOKEN'); - // Expected sign in via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.Auth.prototype, - 'finishPopupAndRedirectSignIn', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - expectedPopupResult = { - 'user': user1, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - // User 1 should be set here and saved to storage. - auth1.setCurrentUser_(user1); - return currentUserStorageManager.setCurrentUser(user1).then(function() { - return expectedPopupResult; - }); - }); - var user1, expectedPopupResult; - asyncTestCase.waitForSignals(2); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - var currentUserStorageManager = - new fireauth.storage.UserManager(config3['apiKey'] + ':' + appId1); - // Simulate persistence set to session on previous page. - currentUserStorageManager.setPersistence('session'); - currentUserStorageManager.savePersistenceForRedirect().then(function() { - // Set pending redirect. - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Get redirect result should resolve with the expected user and - // credential. - auth1.getRedirectResult().then(function(result) { - // Expected result returned. - assertObjectEquals(expectedPopupResult, result); - // User should be stored in session storage since - // savePersistenceForRedirect was previously called with session - // persistence. - return fireauth.common.testHelper.assertUserStorage( - auth1.getStorageKey(), 'session', user1, - fireauth.authStorage.Manager.getInstance()); - }).then(function() { - asyncTestCase.signal(); - }); - // Track all token changes. Confirm persistence changes do not trigger - // unexpected calls. - var uidsDetected = []; - auth1.onIdTokenChanged(function(user) { - // Keep track of UIDs detected on each call. - uidsDetected.push(user && user.uid); - if (uidsDetected.length == 1) { - assertArrayEquals([user1.uid], uidsDetected); - asyncTestCase.signal(); - } else if (uidsDetected.length > 1) { - fail('Unexpected token change!'); - } - }); - }); - }); -} - - -function testAuth_changedPersistence_returnFromRedirect() { - // If persistence is specified after initialization, getRedirectResult will - // resolve with the user stored with expected persistence and not the saved - // one. - fireauth.AuthEventManager.ENABLED = true; - // Expected sign in via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - // Simulate successful verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - function(data) { - assertObjectEquals( - { - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - return goog.Promise.resolve(expectedTokenResponseWithIdPData); - }); - // Simulate Auth user successfully initialized from - // finishPopupAndRedirectSignIn. - stubs.replace( - fireauth.AuthUser, - 'initializeFromIdTokenResponse', - function(options, idTokenResponse) { - assertObjectEquals(config3, options); - assertObjectEquals(expectedTokenResponseWithIdPData, idTokenResponse); - return goog.Promise.resolve(user1); - }); - var user1 = new fireauth.AuthUser( - config3, expectedTokenResponse, accountInfo); - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.SIGN_IN - }; - asyncTestCase.waitForSignals(2); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - var currentUserStorageManager = - new fireauth.storage.UserManager(config3['apiKey'] + ':' + appId1); - // Simulate persistence set to session on previous page. - currentUserStorageManager.setPersistence('session'); - currentUserStorageManager.savePersistenceForRedirect().then(function() { - // Set pending redirect. - pendingRedirectManager.setPendingStatus().then(function() { - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - // Switch persistence to local. - auth1.setPersistence('local'); - // Get redirect result should resolve with the expected user and - // credential. - auth1.getRedirectResult().then(function(result) { - // Expected result returned. - assertObjectEquals(expectedPopupResult, result); - // Even though previous persistence set via savePersistenceForRedirect - // was session, it will be overriddent by the local persistence - // explicitly called after Auth instance is initialized. - return fireauth.common.testHelper.assertUserStorage( - auth1.getStorageKey(), 'local', user1, - fireauth.authStorage.Manager.getInstance()); - }).then(function() { - asyncTestCase.signal(); - }); - // Track all token changes. Confirm persistence changes are not triggered - // unexpected calls. - var uidsDetected = []; - auth1.onIdTokenChanged(function(user) { - // Keep track of all UIDs on each call. - uidsDetected.push(user && user.uid); - if (uidsDetected.length == 1) { - assertArrayEquals([user1.uid], uidsDetected); - asyncTestCase.signal(); - } else if (uidsDetected.length > 1) { - fail('Unexpected token change!'); - } - }); - }); - }); -} - - -/** - * Tests a multi-factor user signing in with a first factor credential and - * then recovering with a second factor assertion. - */ -function testSignInWithCredential_multiFactor_success() { - // Restore the mock reload method from setup. - stubs.restore( - fireauth.AuthUser.prototype, - 'reload'); - // Second factor requirement error returned from first factor sign-in. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var mockCredential = mockControl.createStrictMock( - fireauth.AuthCredential); - // Simulate first factor sign-in triggers second factor requirement. - mockCredential.getIdTokenProvider(ignoreArgument) - .$once() - .$does(function(rpcHandler) { - assertObjectEquals(auth1.getRpcHandler(), rpcHandler); - return goog.Promise.reject(serverResponseError); - }); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(auth1.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(multiFactorTokenResponse.idToken) - .$once() - .$returns(goog.Promise.resolve(multiFactorGetAccountInfoResponse)); - - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - - var authStateListener = mockControl.createFunctionMock('authStateListener'); - var idTokenListener = mockControl.createFunctionMock('idTokenListener'); - auth1.onAuthStateChanged(authStateListener); - auth1.onIdTokenChanged(idTokenListener); - // The listeners will first be triggered right after registration with null - // user. The second time it will be triggered with the multi-factor user. - authStateListener(null).$once(); - authStateListener(ignoreArgument).$once() - .$does(function(user) { - assertEquals(auth1.currentUser, user); - }); - idTokenListener(null).$once(); - idTokenListener(ignoreArgument).$once() - .$does(function(user) { - assertEquals(auth1.currentUser, user); - }); - mockControl.$replayAll(); - - var unsubscribe = auth1.onAuthStateChanged(function(currentUser) { - // Sign in after the first time the listener being triggered with null - // user. Otherwise, since the listener is async, when the listener is - // triggered, the sign-in is not guaranteed to be completed. - auth1.signInAndRetrieveDataWithCredential(mockCredential) - .then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth1, error.resolver.auth); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - auth1.currentUser, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - fireauth.constants.OperationType.SIGN_IN, - result); - // Enrolled factors updated. - assertArrayEquals( - [ - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[0]), - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[1]) - ], - auth1.currentUser.multiFactor.enrolledFactors); - assertEquals('defaultUserId', auth1.currentUser['uid']); - asyncTestCase.signal(); - }); - unsubscribe(); - }); -} - - -/** - * Tests a multi-factor user signing in with a first factor credential and - * then failing to recover with a second factor assertion. - */ -function testSignInWithCredential_multiFactor_assertionError() { - // Restore the mock reload method from setup. - stubs.restore( - fireauth.AuthUser.prototype, - 'reload'); - // Expected assertion processing error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CODE_EXPIRED); - // Second factor requirement error returned from first factor sign-in. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var mockCredential = mockControl.createStrictMock( - fireauth.AuthCredential); - // Simulate first factor sign-in triggers second factor requirement. - mockCredential.getIdTokenProvider(ignoreArgument) - .$once() - .$does(function(rpcHandler) { - assertObjectEquals(auth1.getRpcHandler(), rpcHandler); - return goog.Promise.reject(serverResponseError); - }); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(auth1.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.reject(expectedError); - }); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(ignoreArgument) - .$times(0); - - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - - var authStateListener = mockControl.createFunctionMock('authStateListener'); - var idTokenListener = mockControl.createFunctionMock('idTokenListener'); - auth1.onIdTokenChanged(idTokenListener); - auth1.onAuthStateChanged(authStateListener); - authStateListener(null).$once(); - idTokenListener(null).$once(); - mockControl.$replayAll(); - - // Give enough time for listeners above to trigger before test ends. - var unsubscribe = auth1.onAuthStateChanged(function(currentUser) { - auth1.signInWithCredential(mockCredential).then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth1, error.resolver.auth); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(fail, function(error) { - // Assertion error should be caught. - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); - unsubscribe(); - }); -} - - -/** - * Tests a multi-factor user signing in with a popup and then recovering - * with a second factor assertion. - */ -function testAuth_signInWithPopup_multiFactor_success() { - // Restore the mock reload method from setup. - stubs.restore( - fireauth.AuthUser.prototype, - 'reload'); - fireauth.AuthEventManager.ENABLED = true; - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - var recordedHandler = null; - // The expected popup event ID. - var expectedEventId = '1234'; - // Expected sign in via popup successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - // Generate expected event ID for popup. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Stub instantiateOAuthSignInHandler and save event dispatcher. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - recordedHandler = handler; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; }, - 'processPopup': function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }, - 'startPopupTimeout': function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - } - }; - }); - - // Second factor requirement error returned from first factor sign-in. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var verifyAssertion = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'verifyAssertion'); - // Simulate first factor sign-in triggers second factor requirement. - verifyAssertion({ - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }).$does(function(request) { - return goog.Promise.reject(serverResponseError); - }); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(auth1.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(multiFactorTokenResponse.idToken) - .$once() - .$returns(goog.Promise.resolve(multiFactorGetAccountInfoResponse)); - - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - var provider = new fireauth.GoogleAuthProvider(); - - var authStateListener = mockControl.createFunctionMock('authStateListener'); - var idTokenListener = mockControl.createFunctionMock('idTokenListener'); - auth1.onAuthStateChanged(authStateListener); - auth1.onIdTokenChanged(idTokenListener); - // The listeners will first be triggered right after registration with null - // user. The second time it will be triggered with the multi-factor user. - authStateListener(null).$once(); - authStateListener(ignoreArgument).$once() - .$does(function(user) { - assertEquals(auth1.currentUser, user); - }); - idTokenListener(null).$once(); - idTokenListener(ignoreArgument).$once() - .$does(function(user) { - assertEquals(auth1.currentUser, user); - }); - mockControl.$replayAll(); - - var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { - // Sign in after the first time the listener being triggered with null - // user. Otherwise, since the listener is async, when the listener is - // triggered, the sign-in is not guaranteed to be completed. - // Sign in with popup should reject with MFA_REQUIRED error. - auth1.signInWithPopup(provider).then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth1, error.resolver.auth); - return error.resolver.resolveSignIn(mockAssertion); - }).then(function(popupResult) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - auth1.currentUser, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - fireauth.constants.OperationType.SIGN_IN, - popupResult); - // Enrolled factors updated. - assertArrayEquals( - [ - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[0]), - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[1]) - ], - auth1.currentUser.multiFactor.enrolledFactors); - assertEquals('defaultUserId', auth1.currentUser['uid']); - asyncTestCase.signal(); - }); - unsubscribe(); - }); -} - - -/** - * Tests a multi-factor user returning from sign-in with a redirect and then - * recovering with a second factor assertion. - */ -function testAuth_returnFromSignInWithRedirect_multiFactor_success() { - // Restore the mock reload method from setup. - stubs.restore( - fireauth.AuthUser.prototype, - 'reload'); - fireauth.AuthEventManager.ENABLED = true; - // Expected sign in via redirect successful Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Stub instantiateOAuthSignInHandler. - stubs.replace( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName) { - return { - 'addAuthEventListener': function(handler) { - // auth1 should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name); - assertTrue(manager.isSubscribed(auth1)); - // In this case run immediately with expected redirect event. - handler(expectedAuthEvent); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { return false; }, - 'hasVolatileStorage': function() { return false; } - }; - }); - - // Second factor requirement error returned from first factor sign-in. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var verifyAssertion = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'verifyAssertion'); - // Simulate first factor sign-in triggers second factor requirement. - verifyAssertion({ - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }).$does(function(request) { - return goog.Promise.reject(serverResponseError); - }); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(auth1.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(multiFactorTokenResponse.idToken) - .$once() - .$returns(goog.Promise.resolve(multiFactorGetAccountInfoResponse)); - - asyncTestCase.waitForSignals(1); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config3['apiKey'] + ':' + appId1); - pendingRedirectManager.setPendingStatus(); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - - var authStateListener = mockControl.createFunctionMock('authStateListener'); - var idTokenListener = mockControl.createFunctionMock('idTokenListener'); - auth1.onAuthStateChanged(authStateListener); - auth1.onIdTokenChanged(idTokenListener); - // The listeners will first be triggered right after registration with null - // user. The second time it will be triggered with the multi-factor user. - authStateListener(null).$once(); - authStateListener(ignoreArgument).$once() - .$does(function(user) { - assertEquals(auth1.currentUser, user); - }); - idTokenListener(null).$once(); - idTokenListener(ignoreArgument).$once() - .$does(function(user) { - assertEquals(auth1.currentUser, user); - }); - mockControl.$replayAll(); - - var mfaError; - var unsubscribe = auth1.onIdTokenChanged(function(currentUser) { - // Finish signing in with the second factor after the first time the - // listener is triggered with a null user. Otherwise, since the listener is - // async, it could be triggered only once with the signed in second factor - // user. - auth1.getRedirectResult().then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth1, error.resolver.auth); - mfaError = error; - // Error in redirect result should be cleared after being returned once. - return auth1.getRedirectResult(); - }).then(function(result) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, undefined, result); - return mfaError.resolver.resolveSignIn(mockAssertion); - }).then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - auth1.currentUser, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - fireauth.constants.OperationType.SIGN_IN, - result); - // Enrolled factors updated. - assertArrayEquals( - [ - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[0]), - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[1]) - ], - auth1.currentUser.multiFactor.enrolledFactors); - assertEquals('defaultUserId', auth1.currentUser['uid']); - asyncTestCase.signal(); - }); - unsubscribe(); - }); -} diff --git a/packages/auth/test/authcredential_test.js b/packages/auth/test/authcredential_test.js deleted file mode 100644 index dea0e6e3862..00000000000 --- a/packages/auth/test/authcredential_test.js +++ /dev/null @@ -1,3625 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for authcredential.js - */ - -goog.provide('fireauth.AuthCredentialTest'); - -goog.require('fireauth.Auth'); -goog.require('fireauth.AuthCredential'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthProvider'); -goog.require('fireauth.EmailAuthCredential'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.FacebookAuthProvider'); -goog.require('fireauth.GithubAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.IdToken'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.OAuthCredential'); -goog.require('fireauth.OAuthProvider'); -goog.require('fireauth.PhoneAuthCredential'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.PhoneMultiFactorInfo'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.SAMLAuthCredential'); -goog.require('fireauth.SAMLAuthProvider'); -goog.require('fireauth.TwitterAuthProvider'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.deprecation'); -goog.require('fireauth.idp.ProviderId'); -goog.require('fireauth.idp.SignInMethod'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.AuthCredentialTest'); - - -var mockControl; -var stubs = new goog.testing.PropertyReplacer(); -var rpcHandler = new fireauth.RpcHandler('apiKey'); -var responseForIdToken; - -var app = firebase.initializeApp({ - apiKey: 'myApiKey' -}); -var auth = new fireauth.Auth(app); -var jwt = fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password' - } -}); -var factorDisplayName = 'Work phone number'; -var pendingCredential = 'MFA_PENDING_CREDENTIAL'; -var successTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }), - 'refreshToken': 'REFRESH_TOKEN' -}; -var now = new Date(); - - -function setUp() { - responseForIdToken = { - 'idToken': 'ID_TOKEN' - }; - stubs.replace( - fireauth.util, - 'getCurrentUrl', - function() { - // Simulates a non http://localhost current URL. - return 'http://www.example.com'; - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertion', - goog.testing.recordFunction(function(request) { - return goog.Promise.resolve(responseForIdToken); - })); - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyPassword', - goog.testing.recordFunction(function(request) { - return goog.Promise.resolve(responseForIdToken); - })); - stubs.replace( - fireauth.RpcHandler.prototype, 'emailLinkSignIn', - goog.testing.recordFunction(function(request) { - return goog.Promise.resolve(responseForIdToken); - })); - stubs.replace( - fireauth.RpcHandler.prototype, 'emailLinkSignInForLinking', - goog.testing.recordFunction(function(request) { - return goog.Promise.resolve(responseForIdToken); - })); - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - goog.testing.recordFunction(function(request) { - return goog.Promise.resolve(responseForIdToken); - })); - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - goog.testing.recordFunction(function(request) { - return goog.Promise.resolve(responseForIdToken); - })); - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmailAndPassword', - goog.testing.recordFunction(goog.Promise.resolve)); - - // Internally we should not be using any deprecated methods. - stubs.replace(fireauth.deprecation, 'log', function(message) { - fail('Deprecation message unexpectedly displayed: ' + message); - }); - mockControl = new goog.testing.MockControl(); -} - - -function tearDown() { - mockControl.$verifyAll(); - mockControl.$tearDown(); - stubs.reset(); -} - - -/** - * Initialize the IdToken mocks for parsing an expected ID token and returning - * the expected UID string. - * @param {?string|undefined} expectedIdToken The expected ID token string. - * @param {string} expectedUid The expected UID to be returned if the token is - * valid. - */ -function initializeIdTokenMocks(expectedIdToken, expectedUid) { - // Mock idToken parsing. - var tokenInstance = mockControl.createStrictMock(fireauth.IdToken); - var idTokenParse = mockControl.createMethodMock(fireauth.IdToken, 'parse'); - if (!!expectedIdToken) { - // Valid expected ID token string. - idTokenParse(expectedIdToken).$returns(tokenInstance).$once(); - // Return expected token UID when getLocalId() called. - tokenInstance.getLocalId().$returns(expectedUid); - } else { - // No ID token string provided. - idTokenParse(expectedIdToken).$returns(null).$once(); - } - mockControl.$replayAll(); -} - - -/** - * Assert that the correct request is sent to RPC handler - * verifyAssertionFor. - * @param {?Object} request The verifyAssertion request. - */ -function assertRpcHandlerVerifyAssertion(request) { - assertEquals( - 1, - fireauth.RpcHandler.prototype.verifyAssertion.getCallCount()); - assertObjectEquals( - request, - fireauth.RpcHandler.prototype.verifyAssertion - .getLastCall() - .getArgument(0)); -} - - -/** - * Asserts that the correct request is sent to RPC handler verifyPassword. - * @param {string} email The email in verifyPassword request. - * @param {string} password The password in verifyPassword request. - */ -function assertRpcHandlerVerifyPassword(email, password) { - assertEquals( - 1, - fireauth.RpcHandler.prototype.verifyPassword.getCallCount()); - assertObjectEquals( - email, - fireauth.RpcHandler.prototype.verifyPassword - .getLastCall() - .getArgument(0)); - assertObjectEquals( - password, - fireauth.RpcHandler.prototype.verifyPassword - .getLastCall() - .getArgument(1)); -} - - -/** - * Asserts that the correct request is sent to RPC handler emailLinkSignIn. - * @param {string} email The email in emailLinkSignIn request. - * @param {string} oobCode The oobCode in emailLinkSignIn request. - */ -function assertRpcHandlerEmailLinkSignIn(email, oobCode) { - assertEquals(1, fireauth.RpcHandler.prototype.emailLinkSignIn.getCallCount()); - assertObjectEquals( - email, - fireauth.RpcHandler.prototype.emailLinkSignIn.getLastCall().getArgument( - 0)); - assertObjectEquals( - oobCode, - fireauth.RpcHandler.prototype.emailLinkSignIn.getLastCall().getArgument( - 1)); -} - - -/** - * Asserts that the correct request is sent to RPC handler - * emailLinkSignInForLinking. - * @param {string} idToken The idToken in emailLinkSignInForLinking request. - * @param {string} email The email in emailLinkSignInForLinking request. - * @param {string} oobCode The oobCode in emailLinkSignInForLinking request. - */ -function assertRpcHandlerEmailLinkSignInForLinking(idToken, email, oobCode) { - assertEquals(1, fireauth.RpcHandler.prototype.emailLinkSignInForLinking - .getCallCount()); - assertObjectEquals( - idToken, fireauth.RpcHandler.prototype.emailLinkSignInForLinking - .getLastCall().getArgument(0)); - assertObjectEquals( - email, fireauth.RpcHandler.prototype.emailLinkSignInForLinking - .getLastCall().getArgument(1)); - assertObjectEquals( - oobCode, fireauth.RpcHandler.prototype.emailLinkSignInForLinking - .getLastCall().getArgument(2)); -} - - -/** - * Assert that the correct request is sent to RPC handler - * verifyAssertionForLinking. - * @param {?Object} request The verifyAssertionForLinking request. - */ -function assertRpcHandlerVerifyAssertionForLinking(request) { - assertEquals( - 1, - fireauth.RpcHandler.prototype.verifyAssertionForLinking.getCallCount()); - assertObjectEquals( - request, - fireauth.RpcHandler.prototype.verifyAssertionForLinking - .getLastCall() - .getArgument(0)); -} - - -/** - * Assert that the correct request is sent to RPC handler - * verifyAssertionForExisting. - * @param {?Object} request The verifyAssertionForLinking request. - */ -function assertRpcHandlerVerifyAssertionForExisting(request) { - assertEquals( - 1, - fireauth.RpcHandler.prototype.verifyAssertionForExisting.getCallCount()); - assertObjectEquals( - request, - fireauth.RpcHandler.prototype.verifyAssertionForExisting - .getLastCall() - .getArgument(0)); -} - - -/** - * Assert that the correct request is sent to RPC handler - * updateEmailAndPassword. - * @param {!string} idToken The ID token in updateEmailAndPassword request. - * @param {!string} email The email in updateEmailAndPassword request. - * @param {!string} password The password in updateEmailAndPassword request. - */ -function assertRpcHandlerUpdateEmailAndPassword(idToken, email, password) { - assertEquals( - 1, - fireauth.RpcHandler.prototype.updateEmailAndPassword.getCallCount()); - assertObjectEquals( - idToken, - fireauth.RpcHandler.prototype.updateEmailAndPassword - .getLastCall() - .getArgument(0)); - assertObjectEquals( - email, - fireauth.RpcHandler.prototype.updateEmailAndPassword - .getLastCall() - .getArgument(1)); - assertObjectEquals( - password, - fireauth.RpcHandler.prototype.updateEmailAndPassword - .getLastCall() - .getArgument(2)); -} - - -/** - * Test invalid Auth credential. - */ -function testInvalidCredential() { - var errorOAuth1 = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'credential failed: expected 2 arguments ' + - '(the OAuth access token and secret).'); - var errorOAuth2 = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'credential failed: expected 1 argument ' + - '(the OAuth access token).'); - var errorOidc = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'credential failed: must provide the ID token and/or the access ' + - 'token.'); - - try { - fireauth.FacebookAuthProvider.credential(''); - fail('Should have triggered an invalid Auth credential error!'); - } catch (e) { - fireauth.common.testHelper.assertErrorEquals(errorOAuth2, e); - } - try { - fireauth.GithubAuthProvider.credential(''); - fail('Should have triggered an invalid Auth credential error!'); - } catch (e) { - fireauth.common.testHelper.assertErrorEquals(errorOAuth2, e); - } - try { - fireauth.GoogleAuthProvider.credential('', ''); - fail('Should have triggered an invalid Auth credential error!'); - } catch (e) { - fireauth.common.testHelper.assertErrorEquals(errorOidc, e); - } - try { - fireauth.TwitterAuthProvider.credential('twitterAccessToken', ''); - fail('Should have triggered an invalid Auth credential error!'); - } catch (e) { - fireauth.common.testHelper.assertErrorEquals(errorOAuth1, e); - } - try { - new fireauth.OAuthProvider('example.com').credential('', ''); - fail('Should have triggered an invalid Auth credential error!'); - } catch (e) { - fireauth.common.testHelper.assertErrorEquals(errorOidc, e); - } - // Test invalid credentials from response. - // Empty response. - assertNull(fireauth.AuthProvider.getCredentialFromResponse({})); - // Missing OAuth response. - assertNull( - fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'facebook.com' - })); -} - - -function testOAuthCredential() { - var provider = new fireauth.OAuthProvider('example.com'); - var authCredential = provider.credential({ - 'idToken': 'exampleIdToken', - 'accessToken': 'exampleAccessToken' - }); - assertEquals('exampleIdToken', authCredential['idToken']); - assertEquals('exampleAccessToken', authCredential['accessToken']); - assertEquals('example.com', authCredential['providerId']); - assertEquals('example.com', authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'oauthAccessToken': 'exampleAccessToken', - 'oauthIdToken': 'exampleIdToken', - 'providerId': 'example.com', - 'signInMethod': 'example.com' - }, - authCredential.toPlainObject()); - assertRpcHandlerVerifyAssertion({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'id_token=exampleIdToken&access_token=exampleAccessToken' + - '&providerId=example.com' - }); - - // Test toJSON and fromJSON for current OAuthCredential. - assertObjectEquals( - authCredential, - fireauth.OAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject( - JSON.stringify(authCredential.toPlainObject()))); -} - - -function testOAuthCredential_oldAPI() { - var provider = new fireauth.OAuthProvider('example.com'); - // Initialize the OAuth credential using the old API. - var authCredential = provider.credential( - 'exampleIdToken', 'exampleAccessToken'); - assertEquals('exampleIdToken', authCredential['idToken']); - assertEquals('exampleAccessToken', authCredential['accessToken']); - assertEquals('example.com', authCredential['providerId']); - assertEquals('example.com', authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'oauthAccessToken': 'exampleAccessToken', - 'oauthIdToken': 'exampleIdToken', - 'providerId': 'example.com', - 'signInMethod': 'example.com' - }, - authCredential.toPlainObject()); - assertRpcHandlerVerifyAssertion({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'id_token=exampleIdToken&access_token=exampleAccessToken' + - '&providerId=example.com' - }); - - // Test toJSON and fromJSON for current OAuthCredential. - assertObjectEquals( - authCredential, - fireauth.OAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); -} - - -function testOAuthCredential_idTokenNonce() { - // Test using an OIDC ID token/nonce credential. - var provider = new fireauth.OAuthProvider('oidc.provider'); - var authCredential = provider.credential({ - 'idToken': 'OIDC_ID_TOKEN', - 'rawNonce': 'NONCE' - }); - assertEquals('OIDC_ID_TOKEN', authCredential['idToken']); - assertEquals('NONCE', authCredential['nonce']); - assertUndefined(authCredential['accessToken']); - assertEquals('oidc.provider', authCredential['providerId']); - assertEquals('oidc.provider', authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'nonce': 'NONCE', - 'providerId': 'oidc.provider', - 'signInMethod': 'oidc.provider' - }, - authCredential.toPlainObject()); - - // Confirm expected verifyAssertion request sent. nonce is passed in postBody. - assertRpcHandlerVerifyAssertion({ - 'requestUri': 'http://localhost', - 'postBody': 'id_token=OIDC_ID_TOKEN&providerId=oidc.provider&nonce=NONCE' - }); - - // Test toJSON and fromJSON for current OAuthCredential. - assertObjectEquals( - authCredential, - fireauth.OAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); -} - - -function testOAuthCredential_pendingToken() { - // Test using an OIDC ID token/pending token credential. - var authCredential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - // Nonce should be ignored. - 'nonce': 'NONCE', - 'idToken': 'OIDC_ID_TOKEN' - }, - 'oidc.provider'); - assertEquals('OIDC_ID_TOKEN', authCredential['idToken']); - assertUndefined('NONCE', authCredential['nonce']); - assertUndefined(authCredential['accessToken']); - assertEquals('oidc.provider', authCredential['providerId']); - assertEquals('oidc.provider', authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'providerId': 'oidc.provider', - 'signInMethod': 'oidc.provider' - }, - authCredential.toPlainObject()); - // Confirm only pending token passed in request. - assertRpcHandlerVerifyAssertion({ - 'requestUri': 'http://localhost', - 'pendingToken': 'PENDING_TOKEN' - }); - - // Test toJSON and fromJSON for current OAuthCredential. - assertObjectEquals( - authCredential, - fireauth.OAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); -} - - -function testInvalidOAuthCredential() { - // Test the case where invalid arguments were passed to the OAuthCredential - // constructor. The constructor is only called internally, so the errors are - // internal errors. - try { - new fireauth.OAuthCredential('twitter.com', { - 'oauthToken': 'token' - // OAuth1 secret missing. - }); - fail('Should have triggered an invalid Auth credential error!'); - } catch (e) { - assertEquals('auth/internal-error', e.code); - } -} - - -function testOAuthProvider_constructor() { - var provider = new fireauth.OAuthProvider('example.com'); - assertTrue(provider['isOAuthProvider']); - assertEquals('example.com', provider['providerId']); - // Should not throw an error. - assertNotThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); -} - - -function testOAuthProvider_scopes() { - var provider = new fireauth.OAuthProvider('example.com'); - provider.addScope('scope1'); - assertArrayEquals(['scope1'], provider.getScopes()); - provider.addScope('scope2').addScope('scope3'); - assertArrayEquals(['scope1', 'scope2', 'scope3'], provider.getScopes()); -} - - -function testOAuthProvider_customParameters() { - var provider = new fireauth.OAuthProvider('example.com'); - // Set OAuth custom parameters. - provider.setCustomParameters({ - // Valid OAuth2/OIDC parameters. - 'login_hint': 'user@example.com', - 'prompt': 'consent', - 'include_granted_scopes': true, - // Reserved parameters below should be filtered out. - 'client_id': 'CLIENT_ID', - 'response_type': 'token', - 'scope': 'scope1', - 'redirect_uri': 'https://www.evil.com', - 'state': 'STATE' - }); - // Get custom parameters should only return the valid parameters. - assertObjectEquals({ - 'login_hint': 'user@example.com', - 'prompt': 'consent', - 'include_granted_scopes': 'true', - }, provider.getCustomParameters()); - - // Modify custom parameters. - provider.setCustomParameters({ - 'login_hint': 'user2@example.com' - }).setCustomParameters({ - 'login_hint': 'user3@example.com' - }); - // Parameters should be updated. - assertObjectEquals({ - 'login_hint': 'user3@example.com' - }, provider.getCustomParameters()); -} - - -function testOAuthProvider_chainedMethods() { - // Test that method chaining works. - var provider = new fireauth.OAuthProvider('example.com') - .addScope('scope1') - .addScope('scope2') - .setCustomParameters({ - 'login_hint': 'user@example.com' - }) - .addScope('scope3'); - assertArrayEquals(['scope1', 'scope2', 'scope3'], provider.getScopes()); - assertObjectEquals({ - 'login_hint': 'user@example.com' - }, provider.getCustomParameters()); -} - - -function testOAuthProvider_getCredentialFromResponse() { - var provider = new fireauth.OAuthProvider('example.com'); - var authCredential = provider.credential({ - 'idToken': 'exampleIdToken', - 'accessToken': 'exampleAccessToken' - }); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'oauthAccessToken': 'exampleAccessToken', - 'oauthIdToken': 'exampleIdToken', - 'providerId': 'example.com', - 'signInMethod': 'example.com' - }).toPlainObject()); -} - - -function testOAuthProvider_getCredentialFromResponse_accessTokenOnly() { - // Test Auth credential from response with access token only. - var provider = new fireauth.OAuthProvider('example.com'); - var authCredential = provider.credential({ - 'accessToken': 'exampleAccessToken' - }); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'oauthAccessToken': 'exampleAccessToken', - 'providerId': 'example.com', - 'signInMethod': 'example.com' - }).toPlainObject()); -} - - -function testOAuthProvider_getCredentialFromResponse_idTokenOnly() { - // Test Auth credential from response with ID token only. - var provider = new fireauth.OAuthProvider('example.com'); - var authCredential = provider.credential({ - 'idToken': 'exampleIdToken' - }); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'oauthIdToken': 'exampleIdToken', - 'providerId': 'example.com', - 'signInMethod': 'example.com' - }).toPlainObject()); -} - - -function testOAuthProvider_getCredentialFromResponse_idTokenAndNonce() { - // Test Auth credential from response with ID token/nonce. - var provider = new fireauth.OAuthProvider('example.com'); - var authCredential = provider.credential({ - 'idToken': 'exampleIdToken', - 'rawNonce': 'NONCE' - }); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'oauthIdToken': 'exampleIdToken', - 'providerId': 'example.com', - // This will be injected in rpcHandler. - 'nonce': 'NONCE' - }).toPlainObject()); -} - - -function testOAuthProvider_getCredentialFromResponse_pendingTokenIdToken() { - // Test Auth credential from response with ID token/pending token. - var authCredential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'OIDC_ID_TOKEN' - }, - 'oidc.provider'); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'providerId': 'oidc.provider' - }).toPlainObject()); -} - - -function testOAuthProvider_getCredentialFromResponse_pendingTokenAccessToken() { - // Test Auth credential from response with access token/pending token. - var authCredential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - 'accessToken': 'ACCESS_TOKEN' - }, - 'oidc.provider'); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'oauthAccessToken': 'ACCESS_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'providerId': 'oidc.provider' - }).toPlainObject()); -} - - -function testOAuthProvider_getCredentialFromResponse_pendingTokenAndNonce() { - // Test nonce ignored for pending token. - var authCredential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'OIDC_ID_TOKEN' - }, - 'oidc.provider'); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'providerId': 'oidc.provider', - // This shouldn't be injected but if it did, it will be ignored in - // favor of pending token. - 'nonce': 'NONCE' - }).toPlainObject()); -} - - -function testOAuthCredential_linkToIdToken() { - var provider = new fireauth.OAuthProvider('example.com'); - var authCredential = provider.credential({ - 'idToken': 'exampleIdToken', - 'accessToken': 'exampleAccessToken' - }); - authCredential.linkToIdToken(rpcHandler, 'myIdToken'); - assertRpcHandlerVerifyAssertionForLinking({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'id_token=exampleIdToken&access_token=exampleAccessToken' + - '&providerId=example.com', - 'idToken': 'myIdToken' - }); -} - - -function testOAuthCredential_matchIdTokenWithUid() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - - var provider = new fireauth.OAuthProvider('example.com'); - var authCredential = provider.credential({ - 'idToken': 'exampleIdToken', - 'accessToken': 'exampleAccessToken' - }); - var p = authCredential.matchIdTokenWithUid(rpcHandler, '1234'); - assertRpcHandlerVerifyAssertionForExisting({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'id_token=exampleIdToken&access_token=exampleAccessToken' + - '&providerId=example.com' - }); - return p; -} - - -function testOAuthCredential_withNonce_linkToIdToken() { - var provider = new fireauth.OAuthProvider('oidc.provider'); - var authCredential = provider.credential({ - 'idToken': 'exampleIdToken', - 'rawNonce': 'NONCE' - }); - var p = authCredential.linkToIdToken(rpcHandler, 'myIdToken'); - // Confirm expected verifyAssertion request for linking flow using ID - // token/nonce. - assertRpcHandlerVerifyAssertionForLinking({ - 'requestUri': 'http://localhost', - // nonce is appended to postBody. - 'postBody': 'id_token=exampleIdToken&providerId=oidc.provider' + - '&nonce=NONCE', - 'idToken': 'myIdToken' - }); - return p; -} - - -function testOAuthCredential_withNonce_matchIdTokenWithUid() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - - var provider = new fireauth.OAuthProvider('oidc.provider'); - var authCredential = provider.credential({ - 'idToken': 'exampleIdToken', - 'rawNonce': 'NONCE' - }); - var p = authCredential.matchIdTokenWithUid(rpcHandler, '1234'); - // Confirm expected verifyAssertion request for matchIdToken flow using ID - // token/nonce. - assertRpcHandlerVerifyAssertionForExisting({ - 'requestUri': 'http://localhost', - // nonce is appended to postBody. - 'postBody': 'id_token=exampleIdToken&providerId=oidc.provider' + - '&nonce=NONCE', - }); - return p; -} - - -function testOAuthCredential_withPendingToken_linkToIdToken() { - var authCredential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'OIDC_ID_TOKEN' - }, - 'oidc.provider'); - var p = authCredential.linkToIdToken(rpcHandler, 'myIdToken'); - // Confirm expected verifyAssertion request for linking flow using pending - // token. - assertRpcHandlerVerifyAssertionForLinking({ - 'requestUri': 'http://localhost', - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'myIdToken' - }); - return p; -} - - -function testOAuthCredential_withPendingToken_matchIdTokenWithUid() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - - var authCredential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'OIDC_ID_TOKEN' - }, - 'oidc.provider'); - var p = authCredential.matchIdTokenWithUid(rpcHandler, '1234'); - // Confirm expected verifyAssertion request for match ID token flow using - // pending token. - assertRpcHandlerVerifyAssertionForExisting({ - 'requestUri': 'http://localhost', - 'pendingToken': 'PENDING_TOKEN' - }); - return p; -} - - -function testSAMLAuthCredential() { - var authCredential = new fireauth.SAMLAuthCredential( - 'saml.provider', 'PENDING_TOKEN'); - assertEquals('saml.provider', authCredential['providerId']); - assertEquals('saml.provider', authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'pendingToken': 'PENDING_TOKEN', - 'providerId': 'saml.provider', - 'signInMethod': 'saml.provider' - }, - authCredential.toPlainObject()); - assertRpcHandlerVerifyAssertion({ - 'requestUri': 'http://localhost', - 'pendingToken': 'PENDING_TOKEN' - }); - - // Test toJSON and fromJSON for current SAMLAuthCredential. - assertObjectEquals( - authCredential, - fireauth.SAMLAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject( - JSON.stringify(authCredential.toPlainObject()))); -} - - -function testSAMLAuthCredential_linkToIdToken() { - var authCredential = new fireauth.SAMLAuthCredential( - 'saml.provider', 'PENDING_TOKEN'); - var p = authCredential.linkToIdToken(rpcHandler, 'myIdToken'); - // Confirm expected verifyAssertion request for linking flow using pending - // token. - assertRpcHandlerVerifyAssertionForLinking({ - 'requestUri': 'http://localhost', - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'myIdToken' - }); - return p; -} - - -function testSAMLAuthCredential_matchIdTokenWithUid() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - - var authCredential = new fireauth.SAMLAuthCredential( - 'saml.provider', 'PENDING_TOKEN'); - var p = authCredential.matchIdTokenWithUid(rpcHandler, '1234'); - // Confirm expected verifyAssertion request for match ID token flow using - // pending token. - assertRpcHandlerVerifyAssertionForExisting({ - 'requestUri': 'http://localhost', - 'pendingToken': 'PENDING_TOKEN' - }); - return p; -} - - -function testSAMLAuthProvider_constructor() { - var provider = new fireauth.SAMLAuthProvider('saml.provider'); - assertTrue(provider['isOAuthProvider']); - assertEquals('saml.provider', provider['providerId']); - // Should not throw an error. - assertNotThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); - // This is unused as of now and no reserved parameters used. - var expectedParameters = { - // Valid Facebook OAuth 2.0 parameters. - 'display': 'popup', - 'auth_type': 'rerequest', - 'locale': 'pt_BR', - // Reserved parameters below should be filtered out. - 'client_id': 'CLIENT_ID', - 'response_type': 'token', - 'scope': 'scope1', - 'redirect_uri': 'https://www.evil.com', - 'state': 'STATE' - }; - provider.setCustomParameters(expectedParameters); - assertObjectEquals(expectedParameters, provider.getCustomParameters()); - assertNull(fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'saml.provider' - })); -} - - -function testSAMLAuthCredential_getCredentialFromResponse() { - // Confirm expected SAML Auth credential constructed with SAML pending token. - var authCredential = new fireauth.SAMLAuthCredential( - 'saml.provider', 'PENDING_TOKEN'); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'pendingToken': 'PENDING_TOKEN', - 'providerId': 'saml.provider' - }).toPlainObject()); -} - - -function testSAMLAuthProvider_invalid() { - // SAML provider initialized with an invalid provider ID. - assertThrows(function() { - new fireauth.SAMLAuthProvider('provider.com'); - }); -} - - -/** - * Test Facebook Auth credential. - */ -function testFacebookAuthCredential() { - assertEquals( - fireauth.idp.ProviderId.FACEBOOK, - fireauth.FacebookAuthProvider['PROVIDER_ID']); - assertEquals( - fireauth.idp.SignInMethod.FACEBOOK, - fireauth.FacebookAuthProvider['FACEBOOK_SIGN_IN_METHOD']); - var authCredential = fireauth.FacebookAuthProvider.credential( - 'facebookAccessToken'); - assertEquals('facebookAccessToken', authCredential['accessToken']); - assertEquals(fireauth.idp.ProviderId.FACEBOOK, authCredential['providerId']); - assertEquals( - fireauth.idp.SignInMethod.FACEBOOK, authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'oauthAccessToken': 'facebookAccessToken', - 'providerId': fireauth.idp.ProviderId.FACEBOOK, - 'signInMethod': fireauth.idp.SignInMethod.FACEBOOK - }, - authCredential.toPlainObject()); - - // Test toJSON and fromJSON for current Facebook OAuthCredential. - assertObjectEquals( - authCredential, - fireauth.OAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject( - JSON.stringify(authCredential.toPlainObject()))); - - assertRpcHandlerVerifyAssertion({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'access_token=facebookAccessToken&providerId=' + - fireauth.idp.ProviderId.FACEBOOK - }); - var provider = new fireauth.FacebookAuthProvider(); - // Should not throw an error. - assertNotThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); - assertArrayEquals([], provider.getScopes()); - provider.addScope('scope1'); - assertArrayEquals(['scope1'], provider.getScopes()); - provider.addScope('scope2').addScope('scope3'); - // Add duplicate scope. - provider.addScope('scope1'); - assertArrayEquals(['scope1', 'scope2', 'scope3'], provider.getScopes()); - assertEquals(fireauth.idp.ProviderId.FACEBOOK, provider['providerId']); - assertTrue(provider['isOAuthProvider']); - // Set OAuth custom parameters. - provider.setCustomParameters({ - // Valid Facebook OAuth 2.0 parameters. - 'display': 'popup', - 'auth_type': 'rerequest', - 'locale': 'pt_BR', - // Reserved parameters below should be filtered out. - 'client_id': 'CLIENT_ID', - 'response_type': 'token', - 'scope': 'scope1', - 'redirect_uri': 'https://www.evil.com', - 'state': 'STATE' - }); - // Get custom parameters should only return the valid parameters. - assertObjectEquals({ - 'display': 'popup', - 'auth_type': 'rerequest', - 'locale': 'pt_BR' - }, provider.getCustomParameters()); - // Modify custom parameters. - provider.setCustomParameters({ - // Valid Facebook OAuth 2.0 parameters. - 'auth_type': 'rerequest' - }); - // Parameters should be updated. - assertObjectEquals({ - 'auth_type': 'rerequest' - }, provider.getCustomParameters()); - // Test Auth credential from response. - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'facebook.com', - 'oauthAccessToken': 'facebookAccessToken' - }).toPlainObject()); -} - - -function testFacebookAuthProvider_localization() { - var provider = new fireauth.FacebookAuthProvider(); - // Set default language on provider. - provider.setDefaultLanguage('fr_FR'); - // Default language should be set as custom param. - assertObjectEquals({ - 'locale': 'fr_FR' - }, provider.getCustomParameters()); - // Set some other parameters without the provider's language. - provider.setCustomParameters({ - 'display': 'popup', - 'client_id': 'CLIENT_ID', - 'lang': 'de', - 'hl': 'de' - }); - // The expected parameters include the provider's default language. - assertObjectEquals({ - 'display': 'popup', - 'lang': 'de', - 'hl': 'de', - 'locale': 'fr_FR' - }, provider.getCustomParameters()); - // Set custom parameters with the provider's language. - provider.setCustomParameters({ - 'locale': 'pt_BR', - }); - // Default language should be overwritten. - assertObjectEquals({ - 'locale': 'pt_BR' - }, provider.getCustomParameters()); - // Even after setting the default language, the non-default should still - // apply. - provider.setDefaultLanguage('fr_FR'); - assertObjectEquals({ - 'locale': 'pt_BR' - }, provider.getCustomParameters()); - // Update custom parameters to not include a language field. - provider.setCustomParameters({}); - // Default should apply again. - assertObjectEquals({ - 'locale': 'fr_FR' - }, provider.getCustomParameters()); - // Set default language to null. - provider.setDefaultLanguage(null); - // No language should be returned anymore. - assertObjectEquals({}, provider.getCustomParameters()); -} - - -function testFacebookAuthCredential_alternateConstructor() { - var authCredential = fireauth.FacebookAuthProvider.credential( - {'accessToken': 'facebookAccessToken'}); - assertEquals('facebookAccessToken', authCredential['accessToken']); - assertEquals(fireauth.idp.ProviderId.FACEBOOK, authCredential['providerId']); - assertEquals( - fireauth.idp.SignInMethod.FACEBOOK, authCredential['signInMethod']); - assertObjectEquals( - { - 'oauthAccessToken': 'facebookAccessToken', - 'providerId': fireauth.idp.ProviderId.FACEBOOK, - 'signInMethod': fireauth.idp.SignInMethod.FACEBOOK - }, - authCredential.toPlainObject()); - - // Missing token. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - var error = assertThrows(function() { - fireauth.FacebookAuthProvider.credential({}); - }); - assertEquals(expectedError.code, error.code); -} - - -function testFacebookAuthProvider_chainedMethods() { - // Test that method chaining works. - var provider = new fireauth.FacebookAuthProvider() - .addScope('scope1') - .addScope('scope2') - .setCustomParameters({ - 'locale': 'pt_BR' - }) - .addScope('scope3'); - assertArrayEquals(['scope1', 'scope2', 'scope3'], provider.getScopes()); - assertObjectEquals({ - 'locale': 'pt_BR' - }, provider.getCustomParameters()); -} - - -/** - * Test Facebook Auth credential with non HTTP request. - */ -function testFacebookAuthCredential_nonHttp() { - // Non http or https environment. - stubs.replace( - fireauth.util, - 'getCurrentUrl', - function() {return 'chrome-extension://SOME_LONG_ID';}); - stubs.replace( - fireauth.util, - 'getCurrentScheme', - function() {return 'chrome-extension:';}); - assertEquals( - fireauth.idp.ProviderId.FACEBOOK, - fireauth.FacebookAuthProvider['PROVIDER_ID']); - var authCredential = fireauth.FacebookAuthProvider.credential( - 'facebookAccessToken'); - assertEquals('facebookAccessToken', authCredential['accessToken']); - assertEquals(fireauth.idp.ProviderId.FACEBOOK, authCredential['providerId']); - assertEquals( - fireauth.idp.SignInMethod.FACEBOOK, authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'oauthAccessToken': 'facebookAccessToken', - 'providerId': fireauth.idp.ProviderId.FACEBOOK, - 'signInMethod': fireauth.idp.SignInMethod.FACEBOOK - }, - authCredential.toPlainObject()); - // http://localhost should be used instead of the real current URL. - assertRpcHandlerVerifyAssertion({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'access_token=facebookAccessToken&providerId=' + - fireauth.idp.ProviderId.FACEBOOK - }); -} - - -/** - * Test GitHub Auth credential. - */ -function testGithubAuthCredential() { - assertEquals( - fireauth.idp.ProviderId.GITHUB, - fireauth.GithubAuthProvider['PROVIDER_ID']); - assertEquals( - fireauth.idp.SignInMethod.GITHUB, - fireauth.GithubAuthProvider['GITHUB_SIGN_IN_METHOD']); - var authCredential = fireauth.GithubAuthProvider.credential( - 'githubAccessToken'); - assertEquals('githubAccessToken', authCredential['accessToken']); - assertEquals(fireauth.idp.ProviderId.GITHUB, authCredential['providerId']); - assertEquals( - fireauth.idp.SignInMethod.GITHUB, authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'oauthAccessToken': 'githubAccessToken', - 'providerId': fireauth.idp.ProviderId.GITHUB, - 'signInMethod':fireauth.idp.SignInMethod.GITHUB - }, - authCredential.toPlainObject()); - - // Test toJSON and fromJSON for current GitHub OAuthCredential. - assertObjectEquals( - authCredential, - fireauth.OAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - - assertRpcHandlerVerifyAssertion({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'access_token=githubAccessToken&providerId=' + - fireauth.idp.ProviderId.GITHUB - }); - var provider = new fireauth.GithubAuthProvider(); - // Should not throw an error. - assertNotThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); - assertArrayEquals([], provider.getScopes()); - provider.addScope('scope1'); - assertArrayEquals(['scope1'], provider.getScopes()); - provider.addScope('scope2'); - assertArrayEquals(['scope1', 'scope2'], provider.getScopes()); - assertEquals(fireauth.idp.ProviderId.GITHUB, provider['providerId']); - assertTrue(provider['isOAuthProvider']); - // Set OAuth custom parameters. - provider.setCustomParameters({ - // Valid GitHub OAuth 2.0 parameters. - 'allow_signup': false, - // Reserved parameters below should be filtered out. - 'client_id': 'CLIENT_ID', - 'response_type': 'token', - 'scope': 'scope1', - 'redirect_uri': 'https://www.evil.com', - 'state': 'STATE' - }); - // Get custom parameters should only return the valid parameters. - assertObjectEquals({ - 'allow_signup': 'false' - }, provider.getCustomParameters()); - // Modify custom parameters. - provider.setCustomParameters({ - // Valid GitHub OAuth 2.0 parameters. - 'allow_signup': true - }); - // Parameters should be updated. - assertObjectEquals({ - 'allow_signup': 'true' - }, provider.getCustomParameters()); - // Test Auth credential from response. - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'github.com', - 'oauthAccessToken': 'githubAccessToken' - }).toPlainObject()); -} - - -function testGithubAuthProvider_localization() { - var provider = new fireauth.GithubAuthProvider(); - // Set default language on provider. - provider.setDefaultLanguage('fr'); - // Default language should be ignored as Github doesn't support localization. - assertObjectEquals({}, provider.getCustomParameters()); - // Set all possible language parameters. - provider.setCustomParameters({ - 'locale': 'ar', - 'hl': 'ar', - 'lang': 'ar' - }); - // All language parameters just piped through without the default. - assertObjectEquals({ - 'locale': 'ar', - 'hl': 'ar', - 'lang': 'ar' - }, provider.getCustomParameters()); -} - - -function testOAuthProvider_localization() { - var provider = new fireauth.OAuthProvider('yahoo.com'); - // Set default language on provider. - provider.setDefaultLanguage('fr'); - // Default language should be ignored as generic providers don't support - // default localization. - assertObjectEquals({}, provider.getCustomParameters()); - // Set all possible language parameters. - provider.setCustomParameters({ - 'locale': 'ar', - 'hl': 'ar', - 'lang': 'ar' - }); - // All language parameters just piped through without the default. - assertObjectEquals({ - 'locale': 'ar', - 'hl': 'ar', - 'lang': 'ar' - }, provider.getCustomParameters()); -} - - -function testGithubAuthCredential_alternateConstructor() { - var authCredential = fireauth.GithubAuthProvider.credential( - {'accessToken': 'githubAccessToken'}); - assertEquals('githubAccessToken', authCredential['accessToken']); - assertEquals(fireauth.idp.ProviderId.GITHUB, authCredential['providerId']); - assertObjectEquals( - { - 'oauthAccessToken': 'githubAccessToken', - 'providerId': fireauth.idp.ProviderId.GITHUB, - 'signInMethod':fireauth.idp.SignInMethod.GITHUB - }, - authCredential.toPlainObject()); - - // Missing token. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - var error = assertThrows(function() { - fireauth.GithubAuthProvider.credential({}); - }); - assertEquals(expectedError.code, error.code); -} - - -function testGithubAuthProvider_chainedMethods() { - // Test that method chaining works. - var provider = new fireauth.GithubAuthProvider() - .addScope('scope1') - .addScope('scope2') - .setCustomParameters({ - 'allow_signup': false - }) - .addScope('scope3'); - assertArrayEquals(['scope1', 'scope2', 'scope3'], provider.getScopes()); - assertObjectEquals({ - 'allow_signup': 'false' - }, provider.getCustomParameters()); -} - - -function testGithubAuthCredential_linkToIdToken() { - var authCredential = fireauth.GithubAuthProvider.credential( - 'githubAccessToken'); - authCredential.linkToIdToken(rpcHandler, 'myIdToken'); - assertRpcHandlerVerifyAssertionForLinking({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'access_token=githubAccessToken&providerId=' + - fireauth.idp.ProviderId.GITHUB, - 'idToken': 'myIdToken' - }); -} - - -function testGithubAuthCredential_matchIdTokenWithUid() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - - var authCredential = fireauth.GithubAuthProvider.credential( - 'githubAccessToken'); - var p = authCredential.matchIdTokenWithUid(rpcHandler, '1234'); - assertRpcHandlerVerifyAssertionForExisting({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'access_token=githubAccessToken&providerId=' + - fireauth.idp.ProviderId.GITHUB, - }); - return p; -} - - -/** - * Test Google Auth credential. - */ -function testGoogleAuthCredential() { - assertEquals( - fireauth.idp.ProviderId.GOOGLE, - fireauth.GoogleAuthProvider['PROVIDER_ID']); - assertEquals( - fireauth.idp.SignInMethod.GOOGLE, - fireauth.GoogleAuthProvider['GOOGLE_SIGN_IN_METHOD']); - var authCredential = fireauth.GoogleAuthProvider.credential( - 'googleIdToken', 'googleAccessToken'); - assertEquals('googleIdToken', authCredential['idToken']); - assertEquals('googleAccessToken', authCredential['accessToken']); - assertEquals(fireauth.idp.ProviderId.GOOGLE, authCredential['providerId']); - assertEquals( - fireauth.idp.SignInMethod.GOOGLE, authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertObjectEquals( - { - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'providerId': fireauth.idp.ProviderId.GOOGLE, - 'signInMethod': fireauth.idp.SignInMethod.GOOGLE - }, - authCredential.toPlainObject()); - - // Test toJSON and fromJSON for current Google OAuthCredential. - assertObjectEquals( - authCredential, - fireauth.OAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - - assertRpcHandlerVerifyAssertion({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'id_token=googleIdToken&access_token=googleAccessToken&provi' + - 'derId=' + fireauth.idp.ProviderId.GOOGLE - }); - var provider = new fireauth.GoogleAuthProvider(); - // Should not throw an error. - assertNotThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); - assertArrayEquals(['profile'], provider.getScopes()); - provider.addScope('scope1'); - assertArrayEquals(['profile', 'scope1'], provider.getScopes()); - provider.addScope('scope2'); - assertArrayEquals(['profile', 'scope1', 'scope2'], provider.getScopes()); - assertEquals(fireauth.idp.ProviderId.GOOGLE, provider['providerId']); - assertTrue(provider['isOAuthProvider']); - // Set OAuth custom parameters. - provider.setCustomParameters({ - // Valid Google OAuth 2.0 parameters. - 'login_hint': 'user@example.com', - 'hd': 'example.com', - 'hl': 'fr', - 'prompt': 'consent', - 'include_granted_scopes': true, - // Reserved parameters below should be filtered out. - 'client_id': 'CLIENT_ID', - 'response_type': 'token', - 'scope': 'scope1', - 'redirect_uri': 'https://www.evil.com', - 'state': 'STATE' - }); - // Get custom parameters should only return the valid parameters. - assertObjectEquals({ - 'login_hint': 'user@example.com', - 'hd': 'example.com', - 'hl': 'fr', - 'prompt': 'consent', - 'include_granted_scopes': 'true' - }, provider.getCustomParameters()); - // Modify custom parameters. - provider.setCustomParameters({ - // Valid Google OAuth 2.0 parameters. - 'login_hint': 'user2@example.com' - }); - // Parameters should be updated. - assertObjectEquals({ - 'login_hint': 'user2@example.com' - }, provider.getCustomParameters()); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken' - }).toPlainObject()); - // Test Auth credential from response with access token only. - authCredential = fireauth.GoogleAuthProvider.credential(null, - 'googleAccessToken'); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken' - }).toPlainObject()); - // Test Auth credential from response with ID token only. - authCredential = fireauth.GoogleAuthProvider.credential('googleIdToken'); - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'google.com', - 'oauthIdToken': 'googleIdToken' - }).toPlainObject()); -} - - -function testGoogleAuthProvider_localization() { - var provider = new fireauth.GoogleAuthProvider(); - // Set default language on provider. - provider.setDefaultLanguage('fr'); - // Default language should be set as custom param. - assertObjectEquals({ - 'hl': 'fr' - }, provider.getCustomParameters()); - // Set some other parameters without the provider's language. - provider.setCustomParameters({ - 'prompt': 'consent', - 'client_id': 'CLIENT_ID', - 'lang': 'ar', - 'locale': 'ar' - }); - // The expected parameters include the provider's default language. - assertObjectEquals({ - 'prompt': 'consent', - 'hl': 'fr', - 'lang': 'ar', - 'locale': 'ar' - }, provider.getCustomParameters()); - // Set custom parameters with the provider's language. - provider.setCustomParameters({ - 'hl': 'de' - }); - // Default language should be overwritten. - assertObjectEquals({ - 'hl': 'de' - }, provider.getCustomParameters()); - // Even after setting the default language, the non-default should still - // apply. - provider.setDefaultLanguage('fr'); - assertObjectEquals({ - 'hl': 'de' - }, provider.getCustomParameters()); - // Update custom parameters to not include a language field. - provider.setCustomParameters({}); - // Default should apply again. - assertObjectEquals({ - 'hl': 'fr' - }, provider.getCustomParameters()); - // Set default language to null. - provider.setDefaultLanguage(null); - // No language should be returned anymore. - assertObjectEquals({}, provider.getCustomParameters()); -} - - -function testGoogleAuthProvider_chainedMethods() { - // Test that method chaining works. - var provider = new fireauth.GoogleAuthProvider() - .addScope('scope1') - .addScope('scope2') - .setCustomParameters({ - 'login_hint': 'user@example.com' - }) - .addScope('scope3'); - assertArrayEquals(['profile', 'scope1', 'scope2', 'scope3'], - provider.getScopes()); - assertObjectEquals({ - 'login_hint': 'user@example.com' - }, provider.getCustomParameters()); -} - - -function testGoogleAuthCredential_idTokenConstructor() { - var authCredential = fireauth.GoogleAuthProvider.credential( - 'googleIdToken'); - assertEquals('googleIdToken', authCredential['idToken']); - assertUndefined(authCredential['accessToken']); -} - - -function testGoogleAuthCredential_accessTokenConstructor() { - var authCredential = fireauth.GoogleAuthProvider.credential( - null, 'googleAccessToken'); - assertEquals('googleAccessToken', authCredential['accessToken']); - assertUndefined(authCredential['idToken']); -} - - -function testGoogleAuthCredential_idAndAccessTokenConstructor() { - var authCredential = fireauth.GoogleAuthProvider.credential( - 'googleIdToken', 'googleAccessToken'); - assertEquals('googleIdToken', authCredential['idToken']); - assertEquals('googleAccessToken', authCredential['accessToken']); -} - - -function testGoogleAuthCredential_alternateConstructor() { - // Only ID token. - var authCredentialIdToken = fireauth.GoogleAuthProvider.credential( - {'idToken': 'googleIdToken'}); - assertEquals('googleIdToken', authCredentialIdToken['idToken']); - assertUndefined(authCredentialIdToken['accessToken']); - assertObjectEquals( - { - 'oauthIdToken': 'googleIdToken', - 'providerId': fireauth.idp.ProviderId.GOOGLE, - 'signInMethod': fireauth.idp.SignInMethod.GOOGLE - }, - authCredentialIdToken.toPlainObject()); - - // Only access token. - var authCredentialAccessToken = fireauth.GoogleAuthProvider.credential( - {'accessToken': 'googleAccessToken'}); - assertEquals('googleAccessToken', authCredentialAccessToken['accessToken']); - assertUndefined(authCredentialAccessToken['idToken']); - assertObjectEquals( - { - 'oauthIdToken': 'googleIdToken', - 'providerId': fireauth.idp.ProviderId.GOOGLE, - 'signInMethod': fireauth.idp.SignInMethod.GOOGLE - }, - authCredentialIdToken.toPlainObject()); - - // Both tokens. - var authCredentialBoth = fireauth.GoogleAuthProvider.credential( - {'idToken': 'googleIdToken', 'accessToken': 'googleAccessToken'}); - assertEquals('googleAccessToken', authCredentialBoth['accessToken']); - assertEquals('googleIdToken', authCredentialBoth['idToken']); - assertObjectEquals( - { - 'oauthIdToken': 'googleIdToken', - 'providerId': fireauth.idp.ProviderId.GOOGLE, - 'signInMethod': fireauth.idp.SignInMethod.GOOGLE - }, - authCredentialIdToken.toPlainObject()); - - // Neither token. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - var error = assertThrows(function() { - fireauth.GoogleAuthProvider.credential({}); - }); - assertEquals(expectedError.code, error.code); -} - - -function testGoogleAuthCredential_linkToIdToken() { - var authCredential = fireauth.GoogleAuthProvider.credential( - 'googleIdToken', 'googleAccessToken'); - authCredential.linkToIdToken(rpcHandler, 'myIdToken'); - assertRpcHandlerVerifyAssertionForLinking({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'id_token=googleIdToken&access_token=googleAccessToken&provi' + - 'derId=' + fireauth.idp.ProviderId.GOOGLE, - 'idToken': 'myIdToken' - }); -} - - -function testGoogleAuthCredential_matchIdTokenWithUid() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - - var authCredential = fireauth.GoogleAuthProvider.credential( - 'googleIdToken', 'googleAccessToken'); - var p = authCredential.matchIdTokenWithUid(rpcHandler, '1234'); - assertRpcHandlerVerifyAssertionForExisting({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'id_token=googleIdToken&access_token=googleAccessToken&provi' + - 'derId=' + fireauth.idp.ProviderId.GOOGLE - }); - return p; -} - - -/** - * Test Twitter Auth credential. - */ -function testTwitterAuthCredential() { - assertEquals( - fireauth.idp.ProviderId.TWITTER, - fireauth.TwitterAuthProvider['PROVIDER_ID']); - assertEquals( - fireauth.idp.SignInMethod.TWITTER, - fireauth.TwitterAuthProvider['TWITTER_SIGN_IN_METHOD']); - var authCredential = fireauth.TwitterAuthProvider.credential( - 'twitterOauthToken', 'twitterOauthTokenSecret'); - assertEquals('twitterOauthToken', authCredential['accessToken']); - assertEquals('twitterOauthTokenSecret', authCredential['secret']); - assertEquals(fireauth.idp.ProviderId.TWITTER, authCredential['providerId']); - assertEquals( - fireauth.idp.SignInMethod.TWITTER, authCredential['signInMethod']); - assertObjectEquals( - { - 'oauthAccessToken': 'twitterOauthToken', - 'oauthTokenSecret': 'twitterOauthTokenSecret', - 'providerId': fireauth.idp.ProviderId.TWITTER, - 'signInMethod': fireauth.idp.SignInMethod.TWITTER - }, - authCredential.toPlainObject()); - - // Test toJSON and fromJSON for current Twitter OAuthCredential. - assertObjectEquals( - authCredential, - fireauth.OAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject( - JSON.stringify(authCredential.toPlainObject()))); - - authCredential.getIdTokenProvider(rpcHandler); - assertRpcHandlerVerifyAssertion({ - // requestUri should be http://localhost regardless of current URL. - 'requestUri': 'http://localhost', - 'postBody': 'access_token=twitterOauthToken&oauth_token_secret=twitter' + - 'OauthTokenSecret&providerId=' + - fireauth.idp.ProviderId.TWITTER - }); - var provider = new fireauth.TwitterAuthProvider(); - // Should not throw an error. - assertNotThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); - assertEquals(fireauth.idp.ProviderId.TWITTER, provider['providerId']); - assertTrue(provider['isOAuthProvider']); - // Set OAuth custom parameters. - provider.setCustomParameters({ - 'lang': 'es', - // Reserved parameters below should be filtered out. - 'oauth_consumer_key': 'OAUTH_CONSUMER_KEY', - 'oauth_nonce': 'OAUTH_NONCE', - 'oauth_signature': 'OAUTH_SIGNATURE', - 'oauth_signature_method': 'HMAC-SHA1', - 'oauth_timestamp': '1318622958', - 'oauth_token': 'OAUTH_TOKEN', - 'oauth_version': '1.0' - }); - // Get custom parameters should only return the valid parameters. - assertObjectEquals({ - 'lang': 'es' - }, provider.getCustomParameters()); - // Modify custom parameters. - provider.setCustomParameters({ - 'lang': 'en' - }); - // Parameters should be updated. - assertObjectEquals({ - 'lang': 'en' - }, provider.getCustomParameters()); - // Test Auth credential from response. - assertObjectEquals( - authCredential.toPlainObject(), - fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'twitter.com', - 'oauthAccessToken': 'twitterOauthToken', - 'oauthTokenSecret': 'twitterOauthTokenSecret' - }).toPlainObject()); -} - - -function testTwitterAuthProvider_localization() { - var provider = new fireauth.TwitterAuthProvider(); - // Set default language on provider. - provider.setDefaultLanguage('fr'); - // Default language should be set as custom param. - assertObjectEquals({ - 'lang': 'fr' - }, provider.getCustomParameters()); - // Set some other parameters without the provider's language. - provider.setCustomParameters({ - 'foo': 'bar', - 'oauth_consumer_key': 'OAUTH_CONSUMER_KEY', - 'locale': 'ar', - 'hl': 'ar' - }); - // The expected parameters include the provider's default language. - assertObjectEquals({ - 'foo': 'bar', - 'lang': 'fr', - 'locale': 'ar', - 'hl': 'ar' - }, provider.getCustomParameters()); - // Set custom parameters with the provider's language. - provider.setCustomParameters({ - 'lang': 'de', - }); - // Default language should be overwritten. - assertObjectEquals({ - 'lang': 'de' - }, provider.getCustomParameters()); - // Even after setting the default language, the non-default should still - // apply. - provider.setDefaultLanguage('fr'); - assertObjectEquals({ - 'lang': 'de' - }, provider.getCustomParameters()); - // Update custom parameters to not include a language field. - provider.setCustomParameters({}); - // Default should apply again. - assertObjectEquals({ - 'lang': 'fr' - }, provider.getCustomParameters()); - // Set default language to null. - provider.setDefaultLanguage(null); - // No language should be returned anymore. - assertObjectEquals({}, provider.getCustomParameters()); -} - - -function testTwitterAuthProvider_chainedMethods() { - // Test that method chaining works. - var provider = new fireauth.TwitterAuthProvider() - .setCustomParameters({ - 'lang': 'en' - }) - .setCustomParameters({ - 'lang': 'es' - }); - assertObjectEquals({ - 'lang': 'es' - }, provider.getCustomParameters()); -} - - -function testTwitterAuthCredential_tokenSecretConstructor() { - var authCredential = fireauth.TwitterAuthProvider.credential( - 'twitterOauthToken', 'twitterOauthTokenSecret'); - assertEquals('twitterOauthToken', authCredential['accessToken']); - assertEquals('twitterOauthTokenSecret', authCredential['secret']); -} - - -function testTwitterAuthCredential_alternateConstructor() { - var authCredential = fireauth.TwitterAuthProvider.credential({ - 'oauthToken': 'twitterOauthToken', - 'oauthTokenSecret': 'twitterOauthTokenSecret' - }); - assertEquals('twitterOauthToken', authCredential['accessToken']); - assertEquals('twitterOauthTokenSecret', authCredential['secret']); - assertEquals(fireauth.idp.ProviderId.TWITTER, authCredential['providerId']); - assertObjectEquals( - { - 'oauthAccessToken': 'twitterOauthToken', - 'oauthTokenSecret': 'twitterOauthTokenSecret', - 'providerId': fireauth.idp.ProviderId.TWITTER, - 'signInMethod':fireauth.idp.SignInMethod.TWITTER - }, - authCredential.toPlainObject()); - - // Missing token or secret should be an error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - var error = assertThrows(function() { - fireauth.TwitterAuthProvider.credential({ - 'oauthToken': 'twitterOauthToken' - }); - }); - assertEquals(expectedError.code, error.code); - - error = assertThrows(function() { - fireauth.TwitterAuthProvider.credential({ - 'oauthTokenSecret': 'twitterOauthTokenSecret' - }); - }); - assertEquals(expectedError.code, error.code); -} - - -/** - * Test Email Password Auth credential. - */ -function testEmailAuthCredential() { - assertEquals( - fireauth.idp.ProviderId.PASSWORD, - fireauth.EmailAuthProvider['PROVIDER_ID']); - assertEquals( - fireauth.idp.SignInMethod.EMAIL_PASSWORD, - fireauth.EmailAuthProvider['EMAIL_PASSWORD_SIGN_IN_METHOD']); - var authCredential = fireauth.EmailAuthProvider.credential( - 'user@example.com', 'password'); - assertObjectEquals( - { - 'email': 'user@example.com', - 'password': 'password', - 'signInMethod': 'password' - }, - authCredential.toPlainObject()); - assertEquals(fireauth.idp.ProviderId.PASSWORD, authCredential['providerId']); - authCredential.getIdTokenProvider(rpcHandler); - assertRpcHandlerVerifyPassword('user@example.com', 'password'); - var provider = new fireauth.EmailAuthProvider(); - // Should throw an invalid OAuth provider error. - var error = assertThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - assertEquals(fireauth.idp.ProviderId.PASSWORD, provider['providerId']); - assertFalse(provider['isOAuthProvider']); - - // Test toJSON and fromJSON for current email/password EmailAuthCredential. - assertObjectEquals( - authCredential, - fireauth.EmailAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject( - JSON.stringify(authCredential.toPlainObject()))); -} - - -function testEmailAuthCredential_linkToIdToken() { - var authCredential = fireauth.EmailAuthProvider.credential( - 'foo@bar.com', '123123'); - authCredential.linkToIdToken(rpcHandler, 'myIdToken'); - assertRpcHandlerUpdateEmailAndPassword( - 'myIdToken', 'foo@bar.com', '123123'); -} - - -function testEmailAuthCredentialWithEmailLink_linkToIdToken() { - var authCredential = fireauth.EmailAuthProvider.credentialWithLink( - 'user@example.com', - 'https://www.example.com?mode=signIn&oobCode=code&apiKey=API_KEY'); - authCredential.linkToIdToken(rpcHandler, 'myIdToken'); - assertRpcHandlerEmailLinkSignInForLinking( - 'myIdToken', 'user@example.com', 'code'); -} - - -function testEmailAuthCredential_matchIdTokenWithUid() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - var authCredential = fireauth.EmailAuthProvider.credential( - 'user@example.com', 'password'); - var p = authCredential.matchIdTokenWithUid(rpcHandler, '1234'); - assertRpcHandlerVerifyPassword('user@example.com', 'password'); - return p; -} - - -function testEmailAuthCredentialWithEmailLink_matchIdTokenWithUid() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - var authCredential = fireauth.EmailAuthProvider.credentialWithLink( - 'user@example.com', - 'https://www.example.com?mode=signIn&oobCode=code&apiKey=API_KEY'); - var p = authCredential.matchIdTokenWithUid(rpcHandler, '1234'); - assertRpcHandlerEmailLinkSignIn('user@example.com', 'code'); - return p; -} - - -/** - * Test Email Link Auth credential. - */ -function testEmailAuthCredentialWithLink() { - assertEquals( - fireauth.idp.ProviderId.PASSWORD, - fireauth.EmailAuthProvider['PROVIDER_ID']); - assertEquals( - fireauth.idp.SignInMethod.EMAIL_LINK, - fireauth.EmailAuthProvider['EMAIL_LINK_SIGN_IN_METHOD']); - var authCredential = fireauth.EmailAuthProvider.credentialWithLink( - 'user@example.com', - 'https://www.example.com?mode=signIn&oobCode=code&apiKey=API_KEY'); - assertObjectEquals( - { - 'email': 'user@example.com', - 'password': 'code', - 'signInMethod': 'emailLink' - }, - authCredential.toPlainObject()); - - // Test toJSON and fromJSON for current email/link EmailAuthCredential. - assertObjectEquals( - authCredential, - fireauth.EmailAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject( - JSON.stringify(authCredential.toPlainObject()))); - - assertEquals(fireauth.idp.ProviderId.PASSWORD, authCredential['providerId']); - assertEquals( - fireauth.idp.SignInMethod.EMAIL_LINK, authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertRpcHandlerEmailLinkSignIn('user@example.com', 'code'); - var provider = new fireauth.EmailAuthProvider(); - // Should throw an invalid OAuth provider error. - var error = assertThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - assertEquals(fireauth.idp.ProviderId.PASSWORD, provider['providerId']); - assertFalse(provider['isOAuthProvider']); -} - - -function testEmailAuthCredentialWithLink_deepLink() { - assertEquals( - fireauth.idp.ProviderId.PASSWORD, - fireauth.EmailAuthProvider['PROVIDER_ID']); - assertEquals( - fireauth.idp.SignInMethod.EMAIL_LINK, - fireauth.EmailAuthProvider['EMAIL_LINK_SIGN_IN_METHOD']); - var deepLink = 'https://www.example.com?mode=signIn&oobCode=code' + - '&apiKey=API_KEY'; - var emailLink = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink); - var authCredential = fireauth.EmailAuthProvider.credentialWithLink( - 'user@example.com', emailLink); - assertObjectEquals( - { - 'email': 'user@example.com', - 'password': 'code', - 'signInMethod': 'emailLink' - }, - authCredential.toPlainObject()); - - // Test toJSON and fromJSON for current email/link EmailAuthCredential. - assertObjectEquals( - authCredential, - fireauth.EmailAuthCredential.fromJSON(authCredential.toPlainObject())); - assertObjectEquals( - authCredential, - fireauth.AuthCredential.fromPlainObject(authCredential.toPlainObject())); - - assertEquals(fireauth.idp.ProviderId.PASSWORD, authCredential['providerId']); - assertEquals( - fireauth.idp.SignInMethod.EMAIL_LINK, authCredential['signInMethod']); - authCredential.getIdTokenProvider(rpcHandler); - assertRpcHandlerEmailLinkSignIn('user@example.com', 'code'); - var provider = new fireauth.EmailAuthProvider(); - // Should throw an invalid OAuth provider error. - var error = assertThrows(function() { - fireauth.AuthProvider.checkIfOAuthSupported(provider); - }); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - assertEquals(fireauth.idp.ProviderId.PASSWORD, provider['providerId']); - assertFalse(provider['isOAuthProvider']); -} - - -function testEmailAuthCredentialWithLink_invalidLink_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, 'Invalid email link!'); - var error = assertThrows(function() { - fireauth.EmailAuthProvider.credentialWithLink( - 'user@example.com', 'invalidLink'); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); -} - - -function testEmailAuthProvider_getActionCodeUrlFromSignInEmailLink() { - var emailLink1 = 'https://www.example.com/action?mode=signIn&' + - 'oobCode=oobCode&apiKey=API_KEY'; - var emailLink2 = 'https://www.example.com/action?mode=verifyEmail&' + - 'oobCode=oobCode&apiKey=API_KEY'; - var emailLink3 = 'https://www.example.com/action?mode=signIn'; - var emailLink4 = 'https://www.example.com/action?mode=signIn&' + - 'oobCode=oobCode&apiKey=API_KEY&tenantId=TENANT_ID'; - - var actionCodeUrl1 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink1); - assertEquals('oobCode', actionCodeUrl1['code']); - assertNull(actionCodeUrl1['tenantId']); - var actionCodeUrl2 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink2); - assertNull(actionCodeUrl2); - var actionCodeUrl3 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink3); - assertNull(actionCodeUrl3); - var actionCodeUrl4 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink4); - assertEquals('oobCode', actionCodeUrl4['code']); - assertEquals('TENANT_ID', actionCodeUrl4['tenantId']); -} - - -function testEmailAuthProvider_getActionCodeUrlFromSignInEmailLink_deepLink() { - var deepLink1 = 'https://www.example.com/action?mode=signIn&' + - 'oobCode=oobCode&apiKey=API_KEY'; - var deepLink2 = 'https://www.example.com/action?mode=verifyEmail&' + - 'oobCode=oobCode&apiKey=API_KEY'; - var deepLink3 = 'https://www.example.com/action?mode=signIn'; - var deepLink4 = 'https://www.example.com/action?mode=signIn&' + - 'oobCode=oobCode&apiKey=API_KEY&tenantId=TENANT_ID'; - - var emailLink1 = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink1); - var emailLink2 = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink2); - var emailLink3 = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink3); - var emailLink4 = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(deepLink4); - var emailLink5 = 'comexampleiosurl://google/link?deep_link_id=' + - encodeURIComponent(deepLink1); - - var actionCodeUrl1 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink1); - assertEquals('oobCode', actionCodeUrl1['code']); - assertNull(actionCodeUrl1['tenantId']); - var actionCodeUrl2 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink2); - assertNull(actionCodeUrl2); - var actionCodeUrl3 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink3); - assertNull(actionCodeUrl3); - var actionCodeUrl4 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink4); - assertEquals('oobCode', actionCodeUrl4['code']); - assertEquals('TENANT_ID', actionCodeUrl4['tenantId']); - var actionCodeUrl5 = fireauth.EmailAuthProvider - .getActionCodeUrlFromSignInEmailLink(emailLink5); - assertEquals('oobCode', actionCodeUrl5['code']); -} - - -function testPhoneAuthProvider() { - assertEquals(fireauth.PhoneAuthProvider['PROVIDER_ID'], - fireauth.idp.ProviderId.PHONE); - var provider = new fireauth.PhoneAuthProvider(auth); - assertEquals(provider['providerId'], fireauth.idp.ProviderId.PHONE); -} - - -function testPhoneAuthProvider_noAuth() { - stubs.set(firebase, 'auth', function() { - throw new Error('app not initialized'); - }); - var error = assertThrows(function() { - new fireauth.PhoneAuthProvider(); - }); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - assertEquals(expectedError.code, error.code); -} - - -function testVerifyPhoneNumber() { - var phoneNumber = '+16505550101'; - var recaptchaToken = 'theRecaptchaToken'; - var verificationId = 'theVerificationId'; - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - } - }; - var expectedSendVerificationCodeRequest = { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.sendVerificationCode(expectedSendVerificationCodeRequest) - .$once() - .$returns(goog.Promise.resolve(verificationId)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber(phoneNumber, applicationVerifier) - .then(function(actualVerificationId) { - assertEquals(verificationId, actualVerificationId); - }); -} - - -function testVerifyPhoneNumber_phoneInfoOptions() { - var phoneNumber = '+16505550101'; - var recaptchaToken = 'theRecaptchaToken'; - var verificationId = 'theVerificationId'; - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - } - }; - var expectedSendVerificationCodeRequest = { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.sendVerificationCode(expectedSendVerificationCodeRequest) - .$once() - .$returns(goog.Promise.resolve(verificationId)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'phoneNumber': phoneNumber - }, applicationVerifier).then(function(actualVerificationId) { - assertEquals(verificationId, actualVerificationId); - }); -} - - -function testVerifyPhoneNumber_reset_sendVerificationCodeTwice() { - var phoneNumber = '+16505550101'; - var recaptchaToken1 = 'theRecaptchaToken1'; - var recaptchaToken2 = 'theRecaptchaToken2'; - var verificationId1 = 'theVerificationId1'; - var verificationId2 = 'theVerificationId2'; - var verify = mockControl.createFunctionMock('verify'); - var reset = mockControl.createFunctionMock('reset'); - verify().$once().$returns(goog.Promise.resolve(recaptchaToken1)); - reset().$once(); - verify().$once().$returns(goog.Promise.resolve(recaptchaToken2)); - reset().$once(); - - // Everytime after reset being called, verifier returns a new token. - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': verify, - 'reset': reset - }; - var expectedSendVerificationCodeRequest1 = { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken1 - }; - var expectedSendVerificationCodeRequest2 = { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken2 - }; - - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$times(2).$returns(rpcHandler); - rpcHandler.sendVerificationCode(expectedSendVerificationCodeRequest1) - .$once() - .$returns(goog.Promise.resolve(verificationId1)); - rpcHandler.sendVerificationCode(expectedSendVerificationCodeRequest2) - .$once() - .$returns(goog.Promise.resolve(verificationId2)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber(phoneNumber, applicationVerifier) - .then(function(actualVerificationId1) { - assertEquals(verificationId1, actualVerificationId1); - return provider.verifyPhoneNumber(phoneNumber, applicationVerifier) - .then(function(actualVerificationId2) { - assertEquals(verificationId2, actualVerificationId2); - }); - }); -} - - -function testVerifyPhoneNumber_reset_sendVerificationCodeError() { - var phoneNumber = '+16505550101'; - var recaptchaToken = 'theRecaptchaToken'; - var expectedError = 'something bad happened!!!'; - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - }, - 'reset': goog.testing.recordFunction(function() {}) - }; - var expectedSendVerificationCodeRequest = { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.sendVerificationCode(expectedSendVerificationCodeRequest) - .$once() - .$does(function() { - return goog.Promise.reject(expectedError); - }); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber(phoneNumber, applicationVerifier) - .then(fail, function(error) { - assertEquals(1, applicationVerifier.reset.getCallCount()); - assertEquals(expectedError, error); - }); -} - - -function testVerifyPhoneNumber_defaultAuthInstance() { - // Tests that verifyPhoneNumber works when using the default Auth instance. - var phoneNumber = '+16505550101'; - var recaptchaToken = 'theRecaptchaToken'; - var verificationId = 'theVerificationId'; - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - } - }; - var expectedSendVerificationCodeRequest = { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - stubs.set(firebase, 'auth', function() { - return auth; - }); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.sendVerificationCode(expectedSendVerificationCodeRequest) - .$once() - .$returns(goog.Promise.resolve(verificationId)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(); - return provider.verifyPhoneNumber(phoneNumber, applicationVerifier) - .then(function(actualVerificationId) { - assertEquals(verificationId, actualVerificationId); - }); -} - - -function testVerifyPhoneNumber_notRecaptcha() { - var applicationVerifier = { - // The ApplicationVerifier type is not supported. - 'type': 'some-unsupported-type', - 'verify': function() { - return goog.Promise.resolve('some assertion'); - } - }; - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber('+16505550101', applicationVerifier) - .then(fail, function(error) { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - assertEquals(expectedError.code, error.code); - }); -} - - -function testVerifyPhoneNumber_verifierReturnsUnexpectedType() { - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - // The assertion is not a string. - return goog.Promise.resolve(12345); - } - }; - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber('+16505550101', applicationVerifier) - .then(fail, function(error) { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - assertEquals(expectedError.code, error.code); - }); -} - - -function testVerifyPhoneNumber_verifierThrowsError() { - var expectedError = 'something bad happened!!!'; - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - // The verifier throws its own error. - return goog.Promise.reject(expectedError); - } - }; - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber('+16505550101', applicationVerifier) - .then(fail, function(error) { - assertEquals(expectedError, error); - }); -} - - -function testVerifyPhoneNumber_enrollMfa() { - var phoneNumber = '+16505550101'; - var recaptchaToken = 'theRecaptchaToken'; - var verificationId = 'theVerificationId'; - var enrollSession = new fireauth.MultiFactorSession(jwt); - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - } - }; - var expectedEnrollmentRequest = { - 'idToken': jwt, - 'phoneEnrollmentInfo': { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken - } - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaEnrollment(expectedEnrollmentRequest) - .$once() - .$returns(goog.Promise.resolve(verificationId)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'phoneNumber': phoneNumber, - 'session': enrollSession - }, applicationVerifier).then(function(actualVerificationId) { - assertEquals(verificationId, actualVerificationId); - }); -} - - -function testVerifyPhoneNumber_enrollMfa_reset_sendCodeTwice() { - var phoneNumber = '+16505550101'; - var enrollSession = new fireauth.MultiFactorSession(jwt); - var recaptchaToken1 = 'theRecaptchaToken1'; - var recaptchaToken2 = 'theRecaptchaToken2'; - var verificationId1 = 'theVerificationId1'; - var verificationId2 = 'theVerificationId2'; - var verify = mockControl.createFunctionMock('verify'); - var reset = mockControl.createFunctionMock('reset'); - verify().$once().$returns(goog.Promise.resolve(recaptchaToken1)); - reset().$once(); - verify().$once().$returns(goog.Promise.resolve(recaptchaToken2)); - reset().$once(); - - // Everytime after reset being called, verifier returns a new token. - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': verify, - 'reset': reset - }; - var expectedEnrollmentRequest1 = { - 'idToken': jwt, - 'phoneEnrollmentInfo': { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken1 - } - }; - var expectedEnrollmentRequest2 = { - 'idToken': jwt, - 'phoneEnrollmentInfo': { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken2 - } - }; - - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$times(2).$returns(rpcHandler); - rpcHandler.startPhoneMfaEnrollment(expectedEnrollmentRequest1) - .$once() - .$returns(goog.Promise.resolve(verificationId1)); - rpcHandler.startPhoneMfaEnrollment(expectedEnrollmentRequest2) - .$once() - .$returns(goog.Promise.resolve(verificationId2)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'phoneNumber': phoneNumber, - 'session': enrollSession - }, applicationVerifier).then(function(actualVerificationId1) { - assertEquals(verificationId1, actualVerificationId1); - return provider.verifyPhoneNumber({ - 'phoneNumber': phoneNumber, - 'session': enrollSession - }, applicationVerifier); - }).then(function(actualVerificationId2) { - assertEquals(verificationId2, actualVerificationId2); - }); -} - - -function testVerifyPhoneNumber_enrollMfa_reset_sendCodeError() { - var phoneNumber = '+16505550101'; - var enrollSession = new fireauth.MultiFactorSession(jwt); - var recaptchaToken = 'theRecaptchaToken'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - }, - 'reset': goog.testing.recordFunction(function() {}) - }; - var expectedEnrollmentRequest = { - 'idToken': jwt, - 'phoneEnrollmentInfo': { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken - } - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaEnrollment(expectedEnrollmentRequest) - .$once() - .$does(function() { - return goog.Promise.reject(expectedError); - }); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'phoneNumber': phoneNumber, - 'session': enrollSession - }, applicationVerifier).then(fail, function(error) { - assertEquals(1, applicationVerifier.reset.getCallCount()); - assertEquals(expectedError, error); - }); -} - - -function testVerifyPhoneNumber_enrollMfa_getSessionError() { - var phoneNumber = '+16505550101'; - var enrollSession = new fireauth.MultiFactorSession(jwt); - var recaptchaToken = 'theRecaptchaToken'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - }, - 'reset': goog.testing.recordFunction(function() {}) - }; - - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaEnrollment(goog.testing.mockmatchers.ignoreArgument) - .$times(0); - var getRawSession = mockControl.createMethodMock( - fireauth.MultiFactorSession.prototype, 'getRawSession'); - getRawSession().$once().$does(function() { - return goog.Promise.reject(expectedError); - }); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'phoneNumber': phoneNumber, - 'session': enrollSession - }, applicationVerifier).then(fail, function(error) { - assertEquals(1, applicationVerifier.reset.getCallCount()); - assertEquals(expectedError, error); - }); -} - - -function testVerifyPhoneNumber_enrollMfa_defaultAuthInstance() { - // Tests that verifyPhoneNumber works when using the default Auth instance. - var phoneNumber = '+16505550101'; - var enrollSession = new fireauth.MultiFactorSession(jwt); - var recaptchaToken = 'theRecaptchaToken'; - var verificationId = 'theVerificationId'; - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - } - }; - var expectedEnrollmentRequest = { - 'idToken': jwt, - 'phoneEnrollmentInfo': { - 'phoneNumber': phoneNumber, - 'recaptchaToken': recaptchaToken - } - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - stubs.set(firebase, 'auth', function() { - return auth; - }); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaEnrollment(expectedEnrollmentRequest) - .$once() - .$returns(goog.Promise.resolve(verificationId)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(); - return provider.verifyPhoneNumber({ - 'phoneNumber': phoneNumber, - 'session': enrollSession - }, applicationVerifier).then(function(actualVerificationId) { - assertEquals(verificationId, actualVerificationId); - }); -} - - -function testVerifyPhoneNumber_enrollMfa_notRecaptcha() { - var applicationVerifier = { - // The ApplicationVerifier type is not supported. - 'type': 'some-unsupported-type', - 'verify': function() { - return goog.Promise.resolve('some assertion'); - } - }; - var enrollSession = new fireauth.MultiFactorSession(jwt); - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'phoneNumber': '+16505550101', - 'session': enrollSession - }, applicationVerifier).then(fail, function(error) { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - assertEquals(expectedError.code, error.code); - }); -} - - -function testVerifyPhoneNumber_enrollMfa_verifierReturnsUnexpectedType() { - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - // The assertion is not a string. - return goog.Promise.resolve(12345); - } - }; - var enrollSession = new fireauth.MultiFactorSession(jwt); - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'phoneNumber': '+16505550101', - 'session': enrollSession - }, applicationVerifier).then(fail, function(error) { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR); - assertEquals(expectedError.code, error.code); - }); -} - - -function testVerifyPhoneNumber_enrollMfa_verifierThrowsError() { - var expectedError = new Error('verifier error!'); - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - // The verifier throws its own error. - return goog.Promise.reject(expectedError); - } - }; - var enrollSession = new fireauth.MultiFactorSession(jwt); - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'phoneNumber': '+16505550101', - 'session': enrollSession - }, applicationVerifier).then(fail, function(error) { - assertEquals(expectedError, error); - }); -} - - -function testVerifyPhoneNumber_signInMfa_hint() { - var hint = new fireauth.PhoneMultiFactorInfo({ - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'displayName': 'DISPLAY_NAME', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }); - var recaptchaToken = 'theRecaptchaToken'; - var verificationId = 'theVerificationId'; - var signInSession = new fireauth.MultiFactorSession( - null, 'MFA_PENDING_CREDENTIAL'); - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - } - }; - var expectedSignInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'phoneSignInInfo': { - 'recaptchaToken': recaptchaToken - } - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaSignIn(expectedSignInRequest) - .$once() - .$returns(goog.Promise.resolve(verificationId)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'multiFactorHint': hint, - 'session': signInSession - }, applicationVerifier).then(function(actualVerificationId) { - assertEquals(verificationId, actualVerificationId); - }); -} - - -function testVerifyPhoneNumber_signInMfa_uid() { - var recaptchaToken = 'theRecaptchaToken'; - var verificationId = 'theVerificationId'; - var signInSession = new fireauth.MultiFactorSession( - null, 'MFA_PENDING_CREDENTIAL'); - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - } - }; - var expectedSignInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'phoneSignInInfo': { - 'recaptchaToken': recaptchaToken - } - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaSignIn(expectedSignInRequest) - .$once() - .$returns(goog.Promise.resolve(verificationId)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'multiFactorUid': 'ENROLLMENT_UID', - 'session': signInSession - }, applicationVerifier).then(function(actualVerificationId) { - assertEquals(verificationId, actualVerificationId); - }); -} - - -function testVerifyPhoneNumber_signInMfa_reset_sendCodeTwice() { - var hint = new fireauth.PhoneMultiFactorInfo({ - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'displayName': 'DISPLAY_NAME', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }); - var signInSession = new fireauth.MultiFactorSession( - null, 'MFA_PENDING_CREDENTIAL'); - var recaptchaToken1 = 'theRecaptchaToken1'; - var recaptchaToken2 = 'theRecaptchaToken2'; - var verificationId1 = 'theVerificationId1'; - var verificationId2 = 'theVerificationId2'; - var verify = mockControl.createFunctionMock('verify'); - var reset = mockControl.createFunctionMock('reset'); - verify().$once().$returns(goog.Promise.resolve(recaptchaToken1)); - reset().$once(); - verify().$once().$returns(goog.Promise.resolve(recaptchaToken2)); - reset().$once(); - - // Everytime after reset being called, verifier returns a new token. - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': verify, - 'reset': reset - }; - var expectedSignInRequest1 = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'phoneSignInInfo': { - 'recaptchaToken': recaptchaToken1 - } - }; - var expectedSignInRequest2 = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'phoneSignInInfo': { - 'recaptchaToken': recaptchaToken2 - } - }; - - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$times(2).$returns(rpcHandler); - rpcHandler.startPhoneMfaSignIn(expectedSignInRequest1) - .$once() - .$returns(goog.Promise.resolve(verificationId1)); - rpcHandler.startPhoneMfaSignIn(expectedSignInRequest2) - .$once() - .$returns(goog.Promise.resolve(verificationId2)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'multiFactorHint': hint, - 'session': signInSession - }, applicationVerifier).then(function(actualVerificationId1) { - assertEquals(verificationId1, actualVerificationId1); - return provider.verifyPhoneNumber({ - 'multiFactorHint': hint, - 'session': signInSession - }, applicationVerifier); - }).then(function(actualVerificationId2) { - assertEquals(verificationId2, actualVerificationId2); - }); -} - - -function testVerifyPhoneNumber_signInMfa_reset_sendCodeError() { - var hint = new fireauth.PhoneMultiFactorInfo({ - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'displayName': 'DISPLAY_NAME', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }); - var signInSession = new fireauth.MultiFactorSession( - null, 'MFA_PENDING_CREDENTIAL'); - var recaptchaToken = 'theRecaptchaToken'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - }, - 'reset': goog.testing.recordFunction(function() {}) - }; - var expectedSignInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'phoneSignInInfo': { - 'recaptchaToken': recaptchaToken - } - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaSignIn(expectedSignInRequest) - .$once() - .$does(function() { - return goog.Promise.reject(expectedError); - }); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'multiFactorHint': hint, - 'session': signInSession - }, applicationVerifier).then(fail, function(error) { - assertEquals(1, applicationVerifier.reset.getCallCount()); - assertEquals(expectedError, error); - }); -} - - -function testVerifyPhoneNumber_signInMfa_getSessionError() { - var hint = new fireauth.PhoneMultiFactorInfo({ - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'displayName': 'DISPLAY_NAME', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }); - var signInSession = new fireauth.MultiFactorSession( - null, 'MFA_PENDING_CREDENTIAL'); - var recaptchaToken = 'theRecaptchaToken'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - }, - 'reset': goog.testing.recordFunction(function() {}) - }; - - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaSignIn(goog.testing.mockmatchers.ignoreArgument) - .$times(0); - var getRawSession = mockControl.createMethodMock( - fireauth.MultiFactorSession.prototype, 'getRawSession'); - getRawSession().$once().$does(function() { - return goog.Promise.reject(expectedError); - }); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(auth); - return provider.verifyPhoneNumber({ - 'multiFactorHint': hint, - 'session': signInSession - }, applicationVerifier).then(fail, function(error) { - assertEquals(1, applicationVerifier.reset.getCallCount()); - assertEquals(expectedError, error); - }); -} - - -function testVerifyPhoneNumber_signInMfa_defaultAuthInstance() { - // Tests that verifyPhoneNumber works when using the default Auth instance. - var hint = new fireauth.PhoneMultiFactorInfo({ - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'displayName': 'DISPLAY_NAME', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }); - var signInSession = new fireauth.MultiFactorSession( - null, 'MFA_PENDING_CREDENTIAL'); - var recaptchaToken = 'theRecaptchaToken'; - var verificationId = 'theVerificationId'; - - var applicationVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(recaptchaToken); - } - }; - var expectedSignInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'phoneSignInInfo': { - 'recaptchaToken': recaptchaToken - } - }; - var auth = mockControl.createStrictMock(fireauth.Auth); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - stubs.set(firebase, 'auth', function() { - return auth; - }); - auth.getRpcHandler().$once().$returns(rpcHandler); - rpcHandler.startPhoneMfaSignIn(expectedSignInRequest) - .$once() - .$returns(goog.Promise.resolve(verificationId)); - - mockControl.$replayAll(); - - var provider = new fireauth.PhoneAuthProvider(); - return provider.verifyPhoneNumber({ - 'multiFactorHint': hint, - 'session': signInSession - }, applicationVerifier).then(function(actualVerificationId) { - assertEquals(verificationId, actualVerificationId); - }); -} - - -function testPhoneAuthCredential_validateArguments() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var error; - - error = assertThrows(function() { - fireauth.PhoneAuthCredential({verificationId: 'foo'}); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - - error = assertThrows(function() { - fireauth.PhoneAuthCredential({verificationCode: 'foo'}); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - - error = assertThrows(function() { - fireauth.PhoneAuthCredential({temporaryProof: 'foo'}); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - - error = assertThrows(function() { - fireauth.PhoneAuthCredential({phoneNumber: 'foo'}); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - - error = assertThrows(function() { - fireauth.PhoneAuthCredential({ - verificationCode: 'foo', - phoneNumber: 'bar' - }); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - - error = assertThrows(function() { - fireauth.PhoneAuthCredential({ - verificationCode: 'foo', - temporaryProof: 'bar' - }); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); -} - - -function testPhoneAuthCredential() { - var verificationId = 'theVerificationId'; - var verificationCode = 'theVerificationCode'; - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - assertEquals(fireauth.idp.ProviderId.PHONE, credential['providerId']); - assertEquals(fireauth.idp.SignInMethod.PHONE, credential['signInMethod']); - assertEquals( - fireauth.idp.SignInMethod.PHONE, - fireauth.PhoneAuthProvider['PHONE_SIGN_IN_METHOD']); - assertObjectEquals({ - 'providerId': fireauth.idp.ProviderId.PHONE, - 'verificationId': verificationId, - 'verificationCode': verificationCode - }, credential.toPlainObject()); - - assertNull( - fireauth.AuthProvider.getCredentialFromResponse({ - 'providerId': 'phone' - })); - - // Test toJSON and fromJSON for current verificationId/verificationCode - // PhoneAuthCredential. - assertObjectEquals( - credential, - fireauth.PhoneAuthCredential.fromJSON(credential.toPlainObject())); - assertObjectEquals( - credential, - fireauth.AuthCredential.fromPlainObject(credential.toPlainObject())); - assertObjectEquals( - credential, - fireauth.AuthCredential.fromPlainObject( - JSON.stringify(credential.toPlainObject()))); -} - - -function testPhoneAuthCredential_missingFieldsErrors() { - var verificationId = 'theVerificationId'; - var verificationCode = 'theVerificationCode'; - - var error = assertThrows(function() { - fireauth.PhoneAuthProvider.credential('', verificationCode); - }); - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_SESSION_INFO), - error); - - error = assertThrows(function() { - fireauth.PhoneAuthProvider.credential(verificationId, ''); - }); - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE), - error); - - error = assertThrows(function() { - fireauth.PhoneAuthProvider.credential('', ''); - }); - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_SESSION_INFO), - error); -} - - -function testPhoneAuthCredential_getIdTokenProvider() { - var verificationId = 'theVerificationId'; - var verificationCode = 'theVerificationCode'; - - var expectedVerifyPhoneNumberRequest = { - 'sessionInfo': verificationId, - 'code': verificationCode - }; - var verifyPhoneNumberResponse = { - 'idToken': 'myIdToken', - 'refreshToken': 'myRefreshToken', - 'expiresIn': '3600', - 'localId': 'myLocalId', - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.verifyPhoneNumber(expectedVerifyPhoneNumberRequest).$once() - .$returns(goog.Promise.resolve(verifyPhoneNumberResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential.getIdTokenProvider(rpcHandler) - .then(function(response) { - assertObjectEquals(verifyPhoneNumberResponse, response); - }); -} - - -function testPhoneAuthCredential_linkToIdToken() { - var verificationId = 'theVerificationId'; - var verificationCode = 'theVerificationCode'; - var idToken = 'myIdToken'; - - var expectedVerifyPhoneNumberRequest = { - 'idToken': idToken, - 'sessionInfo': verificationId, - 'code': verificationCode - }; - var verifyPhoneNumberResponse = { - 'idToken': 'myNewIdToken', - 'refreshToken': 'myRefreshToken', - 'expiresIn': '3600', - 'localId': 'myLocalId', - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.verifyPhoneNumberForLinking(expectedVerifyPhoneNumberRequest) - .$once() - .$returns(goog.Promise.resolve(verifyPhoneNumberResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential.linkToIdToken(rpcHandler, idToken) - .then(function(response) { - assertObjectEquals(verifyPhoneNumberResponse, response); - }); -} - - -function testPhoneAuthCredential_matchIdTokenWithUid() { - var verificationId = 'theVerificationId'; - var verificationCode = 'theVerificationCode'; - var idToken = 'myIdToken'; - var uid = '1234'; - initializeIdTokenMocks(idToken, uid); - - var expectedVerifyPhoneNumberRequest = { - 'sessionInfo': verificationId, - 'code': verificationCode - }; - var verifyPhoneNumberResponse = { - 'idToken': idToken, - 'refreshToken': 'myRefreshToken', - 'expiresIn': '3600', - 'localId': uid, - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.verifyPhoneNumberForExisting(expectedVerifyPhoneNumberRequest) - .$once() - .$returns(goog.Promise.resolve(verifyPhoneNumberResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential.matchIdTokenWithUid(rpcHandler, uid) - .then(function(response) { - assertObjectEquals(verifyPhoneNumberResponse, response); - }); -} - - -function testPhoneAuthCredential_matchIdTokenWithUid_mismatch() { - var verificationId = 'theVerificationId'; - var verificationCode = 'theVerificationCode'; - var idToken = 'myIdToken'; - var passedUid = '5678'; - var uid = '1234'; - initializeIdTokenMocks(idToken, uid); - - var expectedVerifyPhoneNumberRequest = { - 'sessionInfo': verificationId, - 'code': verificationCode - }; - var verifyPhoneNumberResponse = { - 'idToken': idToken, - 'refreshToken': 'myRefreshToken', - 'expiresIn': '3600', - 'localId': uid, - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.verifyPhoneNumberForExisting(expectedVerifyPhoneNumberRequest) - .$once() - .$returns(goog.Promise.resolve(verifyPhoneNumberResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential.matchIdTokenWithUid(rpcHandler, passedUid) - .then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH), - error); - }); -} - - -function testPhoneAuthCredential_finalizeMfaEnrollment_success() { - var verificationId = 'SESSION_INFO'; - var verificationCode = '123456'; - var expectedEnrollRequest = { - 'idToken': jwt, - 'displayName': factorDisplayName, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var enrollmentRequestIdentifier = { - 'idToken': jwt, - 'displayName': factorDisplayName - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaEnrollment(expectedEnrollRequest) - .$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential - .finalizeMfaEnrollment(rpcHandler, enrollmentRequestIdentifier) - .then(function(result) { - assertObjectEquals(successTokenResponse, result); - }); -} - - -function testPhoneAuthCredential_finalizeMfaEnrollment_success_noDisplayName() { - var verificationId = 'SESSION_INFO'; - var verificationCode = '123456'; - var expectedEnrollRequest = { - 'idToken': jwt, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var enrollmentRequestIdentifier = { - 'idToken': jwt - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaEnrollment(expectedEnrollRequest) - .$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential - .finalizeMfaEnrollment(rpcHandler, enrollmentRequestIdentifier) - .then(function(result) { - assertObjectEquals(successTokenResponse, result); - }); -} - - -function testPhoneAuthCredential_finalizeMfaEnrollment_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CODE_EXPIRED); - var verificationId = 'SESSION_INFO'; - var verificationCode = '123456'; - var expectedEnrollRequest = { - 'idToken': jwt, - 'displayName': factorDisplayName, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var enrollmentRequestIdentifier = { - 'idToken': jwt, - 'displayName': factorDisplayName - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaEnrollment(expectedEnrollRequest) - .$once() - .$returns(goog.Promise.reject(expectedError)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential - .finalizeMfaEnrollment(rpcHandler, enrollmentRequestIdentifier) - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testPhoneAuthCredential_finalizeMfaSignIn_success() { - var verificationId = 'SESSION_INFO'; - var verificationCode = '123456'; - var expectedSignInRequest = { - 'mfaPendingCredential': pendingCredential, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var signInRequestIdentifier = { - 'mfaPendingCredential': pendingCredential - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaSignIn(expectedSignInRequest) - .$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential - .finalizeMfaSignIn(rpcHandler, signInRequestIdentifier) - .then(function(result) { - assertObjectEquals(successTokenResponse, result); - }); -} - - -function testPhoneAuthCredential_finalizeMfaSignIn_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CODE_EXPIRED); - var verificationId = 'SESSION_INFO'; - var verificationCode = '123456'; - var expectedSignInRequest = { - 'mfaPendingCredential': pendingCredential, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var signInRequestIdentifier = { - 'mfaPendingCredential': pendingCredential - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaSignIn(expectedSignInRequest) - .$once() - .$returns(goog.Promise.reject(expectedError)); - - mockControl.$replayAll(); - - var credential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - return credential - .finalizeMfaSignIn(rpcHandler, signInRequestIdentifier) - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testPhoneAuthCredential_temporaryProof() { - var temporaryProof = 'theTempProof'; - var phoneNumber = '+16505550101'; - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }); - - assertEquals(fireauth.idp.ProviderId.PHONE, credential['providerId']); - assertEquals(fireauth.idp.SignInMethod.PHONE, credential['signInMethod']); - assertEquals( - fireauth.idp.SignInMethod.PHONE, - fireauth.PhoneAuthProvider['PHONE_SIGN_IN_METHOD']); - assertObjectEquals({ - 'providerId': fireauth.idp.ProviderId.PHONE, - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }, credential.toPlainObject()); - - // Test toJSON and fromJSON for current temporaryProof PhoneAuthCredential. - assertObjectEquals( - credential, - fireauth.PhoneAuthCredential.fromJSON(credential.toPlainObject())); - assertObjectEquals( - credential, - fireauth.AuthCredential.fromPlainObject(credential.toPlainObject())); -} - - -function testPhoneAuthCredential_temporaryProof_getIdTokenProvider() { - var temporaryProof = 'theTempProof'; - var phoneNumber = '+16505550101'; - - var expectedVerifyPhoneNumberRequest = { - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }; - var verifyPhoneNumberResponse = { - 'idToken': 'myIdToken', - 'refreshToken': 'myRefreshToken', - 'expiresIn': '3600', - 'localId': 'myLocalId', - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.verifyPhoneNumber(expectedVerifyPhoneNumberRequest).$once() - .$returns(goog.Promise.resolve(verifyPhoneNumberResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }); - return credential.getIdTokenProvider(rpcHandler) - .then(function(response) { - assertObjectEquals(verifyPhoneNumberResponse, response); - }); -} - - -function testPhoneAuthCredential_temporaryProof_linkToIdToken() { - var temporaryProof = 'theTempProof'; - var phoneNumber = '+16505550101'; - var idToken = 'myIdToken'; - - var expectedVerifyPhoneNumberRequest = { - 'idToken': idToken, - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }; - var verifyPhoneNumberResponse = { - 'idToken': 'myNewIdToken', - 'refreshToken': 'myRefreshToken', - 'expiresIn': '3600', - 'localId': 'myLocalId', - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.verifyPhoneNumberForLinking(expectedVerifyPhoneNumberRequest) - .$once() - .$returns(goog.Promise.resolve(verifyPhoneNumberResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }); - return credential.linkToIdToken(rpcHandler, idToken) - .then(function(response) { - assertObjectEquals(verifyPhoneNumberResponse, response); - }); -} - - -function testPhoneAuthCredential_temporaryProof_matchIdTokenWithUid() { - var temporaryProof = 'theTempProof'; - var phoneNumber = '+16505550101'; - var idToken = 'myIdToken'; - var uid = '1234'; - initializeIdTokenMocks(idToken, uid); - - var expectedVerifyPhoneNumberRequest = { - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }; - var verifyPhoneNumberResponse = { - 'idToken': idToken, - 'refreshToken': 'myRefreshToken', - 'expiresIn': '3600', - 'localId': uid, - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.verifyPhoneNumberForExisting(expectedVerifyPhoneNumberRequest) - .$once() - .$returns(goog.Promise.resolve(verifyPhoneNumberResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }); - return credential.matchIdTokenWithUid(rpcHandler, uid) - .then(function(response) { - assertObjectEquals(verifyPhoneNumberResponse, response); - }); -} - - -function testPhoneAuthCredential_temporaryProof_matchIdTokenWithUid_mismatch() { - var temporaryProof = 'theTempProof'; - var phoneNumber = '+16505550101'; - var idToken = 'myIdToken'; - var passedUid = '5678'; - var uid = '1234'; - initializeIdTokenMocks(idToken, uid); - - var expectedVerifyPhoneNumberRequest = { - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }; - var verifyPhoneNumberResponse = { - 'idToken': idToken, - 'refreshToken': 'myRefreshToken', - 'expiresIn': '3600', - 'localId': uid, - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.verifyPhoneNumberForExisting(expectedVerifyPhoneNumberRequest) - .$once() - .$returns(goog.Promise.resolve(verifyPhoneNumberResponse)); - - mockControl.$replayAll(); - - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }); - return credential.matchIdTokenWithUid(rpcHandler, passedUid) - .then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH), - error); - }); -} - - -function testVerifyTokenResponseUid_match() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - return fireauth.AuthCredential.verifyTokenResponseUid( - goog.Promise.resolve(responseForIdToken), '1234'); -} - - -function testVerifyTokenResponseUid_idTokenNotFound_mismatch() { - // No ID token returned. - var noIdTokenResponse = {}; - return fireauth.AuthCredential.verifyTokenResponseUid( - goog.Promise.resolve(noIdTokenResponse), '1234') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH), - error); - }); -} - - -function testVerifyTokenResponseUid_userFound_mismatch() { - // Mock idToken parsing. - initializeIdTokenMocks('ID_TOKEN', '1234'); - return fireauth.AuthCredential.verifyTokenResponseUid( - // The UID does not match the ID token UID. - goog.Promise.resolve(responseForIdToken), '5678') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH), - error); - }); -} - - -function testVerifyTokenResponseUid_passThroughError() { - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - return fireauth.AuthCredential.verifyTokenResponseUid( - goog.Promise.reject(expectedError), '1234') - .thenCatch(function(error) { - assertEquals(expectedError, error); - }); -} - - -function testVerifyTokenResponseUid_userNotFound() { - // Confirm USER_DELETED error is translated to USER_MISMATCH. - var error = - new fireauth.AuthError(fireauth.authenum.Error.USER_DELETED); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH); - return fireauth.AuthCredential.verifyTokenResponseUid( - goog.Promise.reject(error), '1234') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} diff --git a/packages/auth/test/authevent_test.js b/packages/auth/test/authevent_test.js deleted file mode 100644 index e86e6c7759a..00000000000 --- a/packages/auth/test/authevent_test.js +++ /dev/null @@ -1,335 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for authevent.js - */ - -goog.provide('fireauth.AuthEventTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.authenum.Error'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.AuthEventTest'); - - -var authEvent; -var authEvent2; -var authEventObject; -var authEventObject2; -var popupType = [ - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP -]; -var redirectType = [ - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT -]; - - -function setUp() { - authEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID', - null, - 'POST_BODY'); - authEvent2 = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - '12345678', - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)); - authEvent3 = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID', - null, - null, - 'TENANT_ID'); - authEvent4 = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - '12345678', - 'http://www.example.com/#oauthResponse', - 'SESSION_ID', - null, - null, - 'TENANT_ID'); - authEventObject = { - 'type': 'signInViaPopup', - 'eventId': null, - 'urlResponse': 'http://www.example.com/#oauthResponse', - 'sessionId': 'SESSION_ID', - 'error': null, - 'postBody': 'POST_BODY', - 'tenantId': null - }; - authEventObject2 = { - 'type': 'signInViaRedirect', - 'eventId': '12345678', - 'urlResponse': null, - 'sessionId': null, - 'error': { - 'code': fireauth.AuthError.ERROR_CODE_PREFIX + - fireauth.authenum.Error.INTERNAL_ERROR, - 'message': 'An internal error has occurred.' - }, - 'postBody': null, - 'tenantId': null - }; - authEventObject3 = { - 'type': 'signInViaRedirect', - 'eventId': null, - 'urlResponse': 'http://www.example.com/#oauthResponse', - 'sessionId': 'SESSION_ID', - 'error': null, - 'postBody': null, - 'tenantId': 'TENANT_ID' - }; - authEventObject4 = { - 'type': 'signInViaPopup', - 'eventId': '12345678', - 'urlResponse': 'http://www.example.com/#oauthResponse', - 'sessionId': 'SESSION_ID', - 'error': null, - 'postBody': null, - 'tenantId': 'TENANT_ID' - }; -} - - -function tearDown() { - authEvent = null; - authEvent2 = null; - authEventObject = null; - authEventObject2 = null; -} - - -/** - * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be - * used as Internet Explorer adds the stack trace as a property of the object. - * @param {!Error} expected - * @param {!Error} actual - */ -function assertErrorEquals(expected, actual) { - assertEquals(expected.code, actual.code); - assertEquals(expected.message, actual.message); -} - - -function testAuthEvent_isRedirect() { - // Popup types should return false. - for (var i = 0; i < popupType.length; i++) { - assertFalse(fireauth.AuthEvent.isRedirect( - new fireauth.AuthEvent( - popupType[i], - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'))); - } - // Unknown event type should return false. - assertFalse(fireauth.AuthEvent.isRedirect( - new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)))); - // verifyApp event type should return false. - assertFalse(fireauth.AuthEvent.isRedirect( - new fireauth.AuthEvent( - fireauth.AuthEvent.Type.VERIFY_APP, - null, - 'http://www.example.com/#oauthResponse', - 'blank'))); - // Redirect types should return true. - for (var i = 0; i < redirectType.length; i++) { - assertTrue(fireauth.AuthEvent.isRedirect( - new fireauth.AuthEvent( - redirectType[i], - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'))); - } -} - - -function testAuthEvent_isPopup() { - // Popup types should return true. - for (var i = 0; i < popupType.length; i++) { - assertTrue(fireauth.AuthEvent.isPopup( - new fireauth.AuthEvent( - popupType[i], - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'))); - } - // Unknown event type should return false. - assertFalse(fireauth.AuthEvent.isPopup( - new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)))); - // verifyApp event type should return false. - assertFalse(fireauth.AuthEvent.isPopup( - new fireauth.AuthEvent( - fireauth.AuthEvent.Type.VERIFY_APP, - null, - 'http://www.example.com/#oauthResponse', - 'blank'))); - // Redirect types should return false. - for (var i = 0; i < redirectType.length; i++) { - assertFalse(fireauth.AuthEvent.isPopup( - new fireauth.AuthEvent( - redirectType[i], - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'))); - } -} - - -function testAuthEvent_error() { - try { - new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP); - fail('Auth event requires either an error or a URL response.'); - } catch(error) { - assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT), - error); - } - try { - new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - '12345678', - 'http://www.example.com/#oauthResponse', - 'SESSION_ID', - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)); - fail('Auth event cannot have a URL response and an error.'); - } catch(error) { - assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT), - error); - } - try { - new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - null, - 'http://www.example.com/#oauthResponse'); - fail('Auth event cannot have a URL response without a session ID.'); - } catch(error) { - assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT), - error); - } -} - - -function testAuthEvent() { - var unknownEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)); - assertEquals('unknown', unknownEvent.getUid()); - - assertEquals( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - authEvent.getType()); - assertEquals( - 'http://www.example.com/#oauthResponse', authEvent.getUrlResponse()); - assertEquals( - 'SESSION_ID', authEvent.getSessionId()); - assertEquals('POST_BODY', authEvent.getPostBody()); - assertNull(authEvent.getEventId()); - assertNull(authEvent.getError()); - assertFalse(authEvent.hasError()); - assertEquals('signInViaPopup-SESSION_ID', authEvent.getUid()); - - assertEquals( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - authEvent2.getType()); - assertEquals('12345678', authEvent2.getEventId()); - assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - authEvent2.getError()); - assertTrue(authEvent2.hasError()); - assertNull(authEvent2.getPostBody()); - assertEquals('signInViaRedirect-12345678', authEvent2.getUid()); - - assertEquals( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - authEvent3.getType()); - assertNull(authEvent3.getEventId()); - assertNull(authEvent3.getError()); - assertFalse(authEvent3.hasError()); - assertNull(authEvent3.getPostBody()); - assertEquals('TENANT_ID', authEvent3.getTenantId()); - - assertEquals( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - authEvent4.getType()); - assertEquals('12345678', authEvent4.getEventId()); - assertNull(authEvent4.getError()); - assertFalse(authEvent4.hasError()); - assertNull(authEvent4.getPostBody()); - assertEquals('TENANT_ID', authEvent4.getTenantId()); -} - - -function testAuthEvent_toPlainObject() { - assertObjectEquals( - authEventObject, - authEvent.toPlainObject()); - assertObjectEquals( - authEventObject2, - authEvent2.toPlainObject()); - assertObjectEquals( - authEventObject3, - authEvent3.toPlainObject()); - assertObjectEquals( - authEventObject4, - authEvent4.toPlainObject()); -} - - -function testAuthEvent_fromPlainObject() { - assertObjectEquals( - authEvent, - fireauth.AuthEvent.fromPlainObject(authEventObject)); - assertObjectEquals( - authEvent2, - fireauth.AuthEvent.fromPlainObject(authEventObject2)); - assertObjectEquals( - authEvent3, - fireauth.AuthEvent.fromPlainObject(authEventObject3)); - assertObjectEquals( - authEvent4, - fireauth.AuthEvent.fromPlainObject(authEventObject4)); - assertNull(fireauth.AuthEvent.fromPlainObject({})); -} diff --git a/packages/auth/test/autheventmanager_test.js b/packages/auth/test/autheventmanager_test.js deleted file mode 100644 index a1ed5270d5e..00000000000 --- a/packages/auth/test/autheventmanager_test.js +++ /dev/null @@ -1,3338 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for autheventmanager.js - */ - -goog.provide('fireauth.AuthEventManagerTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.AuthEventManager'); -goog.require('fireauth.CordovaHandler'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.InvalidOriginError'); -goog.require('fireauth.PopupAuthEventProcessor'); -goog.require('fireauth.RedirectAuthEventProcessor'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.UniversalLinkSubscriber'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.OAuthHandlerManager'); -goog.require('fireauth.storage.PendingRedirectManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Uri'); -goog.require('goog.crypt'); -goog.require('goog.crypt.Sha256'); -goog.require('goog.testing.AsyncTestCase'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.mockmatchers'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.AuthEventManagerTest'); - - -var handler; -var stubs = new goog.testing.PropertyReplacer(); -var appName1 = 'APP1'; -var apiKey1 = 'API_KEY1'; -var authDomain1 = 'subdomain1.firebaseapp.com'; -var appName2 = 'APP2'; -var apiKey2 = 'API_KEY2'; -var authDomain2 = 'subdomain2.firebaseapp.com'; -var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); -// Firebase SDK version in case not available. -firebase.SDK_VERSION = firebase.SDK_VERSION || '3.0.0'; -var clock; -var expectedVersion; -var universalLinks; -var BuildInfo; -var cordova; -var OAuthSignInHandler; -var androidUA = 'Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Buil' + - 'd/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Sa' + - 'fari/534.30'; -var savePartialEventManager; -var timeoutDelay = 30000; -var mockControl; -var ignoreArgument; -var mockLocalStorage; -var mockSessionStorage; - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - mockControl = new goog.testing.MockControl(); - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl.$resetAll(); - handler = { - 'canHandleAuthEvent': goog.testing.recordFunction(), - 'resolvePendingPopupEvent': goog.testing.recordFunction(), - 'getAuthEventHandlerFinisher': goog.testing.recordFunction() - }; - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); - // Default OAuth sign in handler is IfcHandler. - setOAuthSignInHandlerEnvironment(false); -} - - -function tearDown() { - fireauth.AuthEventManager.manager_ = {}; - window.localStorage.clear(); - window.sessionStorage.clear(); - stubs.reset(); - goog.dispose(clock); - // Clear plugins. - universalLinks = {}; - BuildInfo = {}; - cordova = {}; - cordovaHandler = null; - OAuthSignInHandler = null; - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } - fireauth.UniversalLinkSubscriber.clear(); -} - - -/** - * @param {string} str The string to hash. - * @return {string} The hashed string. - */ -function sha256(str) { - var sha256 = new goog.crypt.Sha256(); - sha256.update(str); - return goog.crypt.byteArrayToHex(sha256.digest()); -} - - -/** - * Utility function to initialize the Cordova mock plugins. - * @param {?function(?string, function(!Object))} subscribe The universal link - * subscriber. - * @param {?string} packageName The package name. - * @param {?string} displayName The app display name. - * @param {boolean} isAvailable Whether browsertab is supported. - * @param {?function(string, ?function(), ?function())} openUrl The URL opener. - * @param {?function()} close The browsertab closer if applicable. - * @param {?function()} open The inappbrowser opener if available. - */ -function initializePlugins( - subscribe, packageName, displayName, isAvailable, openUrl, close, open) { - // Initializes all mock plugins. - universalLinks = { - subscribe: subscribe - }; - BuildInfo = { - packageName: packageName, - displayName: displayName - }; - cordova = { - plugins: { - browsertab: { - isAvailable: function(cb) { - cb(isAvailable); - }, - openUrl: openUrl, - close: close - } - }, - InAppBrowser: { - open: open - } - }; -} - - -/** - * Helper function to set the current OAuth sign in handler. - * @param {boolean} isCordova Whether to simulate a Cordova environment. - */ -function setOAuthSignInHandlerEnvironment(isCordova) { - stubs.replace( - fireauth.util, - 'isAndroidOrIosCordovaScheme', - function() { - return isCordova; - }); - stubs.replace( - fireauth.util, - 'checkIfCordova', - function() { - if (isCordova) { - return goog.Promise.resolve(); - } else { - return goog.Promise.reject(); - } - }); - if (isCordova) { - OAuthSignInHandler = fireauth.CordovaHandler; - // Storage manager helpers. - savePartialEventManager = new fireauth.storage.OAuthHandlerManager(); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Initialize plugins. - initializePlugins( - goog.testing.recordFunction(), - 'com.example.app', - 'Test App', - true, - goog.testing.recordFunction(), - goog.testing.recordFunction(), - goog.testing.recordFunction()); - } else { - OAuthSignInHandler = fireauth.iframeclient.IfcHandler; - // Override iframewrapper to prevent iframe from being embedded in tests. - stubs.replace(fireauth.iframeclient, 'IframeWrapper', function(url) { - return { - registerEvent: function(eventName, callback) {}, - onReady: function() { return goog.Promise.resolve(); } - }; - }); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - } -} - - -/** - * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be - * used as Internet Explorer adds the stack trace as a property of the object. - * @param {!fireauth.AuthError} expected - * @param {!fireauth.AuthError} actual - */ -function assertErrorEquals(expected, actual) { - assertObjectEquals(expected.toPlainObject(), actual.toPlainObject()); -} - - -function testGetManager() { - var manager1 = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - assertEquals( - fireauth.AuthEventManager.manager_[ - fireauth.AuthEventManager.getKey_(apiKey1, appName1)], - manager1); - assertEquals( - fireauth.AuthEventManager.getManager(authDomain1, apiKey1, appName1), - manager1); - var manager2 = fireauth.AuthEventManager.getManager( - authDomain2, apiKey2, appName2); - assertEquals( - fireauth.AuthEventManager.manager_[ - fireauth.AuthEventManager.getKey_(apiKey2, appName2)], - manager2); - assertEquals( - fireauth.AuthEventManager.getManager(authDomain2, apiKey2, appName2), - manager2); - var emulatorConfig = { - url: 'http://emulator.test.domain:1234' - }; - var managerWithEmulator = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1, emulatorConfig); - assertEquals( - fireauth.AuthEventManager.manager_[ - fireauth.AuthEventManager.getKey_(apiKey1, appName1)], - manager1); - assertEquals( - fireauth.AuthEventManager.manager_[ - fireauth.AuthEventManager.getKey_(apiKey1, appName1, emulatorConfig)], - managerWithEmulator); -} - - -function testInstantiateOAuthSignInHandler_ifcHandler() { - // Simulate browser environment. - setOAuthSignInHandlerEnvironment(false); - // IfcHandler should be instantiated. - var ifcHandler = mockControl.createStrictMock( - fireauth.iframeclient.IfcHandler); - var ifcHandlerConstructor = mockControl.createConstructorMock( - fireauth.iframeclient, 'IfcHandler'); - // Confirm expected endpoint used. - ifcHandlerConstructor( - authDomain1, apiKey1, appName1, firebase.SDK_VERSION, - fireauth.constants.Endpoint.STAGING.id, ignoreArgument).$returns(ifcHandler); - mockControl.$replayAll(); - - fireauth.AuthEventManager.instantiateOAuthSignInHandler( - authDomain1, apiKey1, appName1, firebase.SDK_VERSION, - fireauth.constants.Endpoint.STAGING.id); -} - - -/** Asserts that emulator config is propagated to ifcHandler. */ -function testInstantiateOAuthSignInHandler_ifcHandler_withEmulator() { - // Simulate browser environment. - setOAuthSignInHandlerEnvironment(false); - // IfcHandler should be instantiated. - var ifcHandler = mockControl.createStrictMock( - fireauth.iframeclient.IfcHandler); - var ifcHandlerConstructor = mockControl.createConstructorMock( - fireauth.iframeclient, 'IfcHandler'); - var emulatorConfig = { - url: 'http://emulator.test.domain:1234' - }; - // Confirm expected endpoint used. - ifcHandlerConstructor( - authDomain1, apiKey1, appName1, firebase.SDK_VERSION, - fireauth.constants.Endpoint.STAGING.id, - emulatorConfig).$returns(ifcHandler); - mockControl.$replayAll(); - - fireauth.AuthEventManager.instantiateOAuthSignInHandler( - authDomain1, apiKey1, appName1, firebase.SDK_VERSION, - fireauth.constants.Endpoint.STAGING.id, emulatorConfig); -} - - -function testInstantiateOAuthSignInHandler_cordovaHandler() { - // Simulate Cordova environment - setOAuthSignInHandlerEnvironment(true); - // CordovaHandler should be instantiated. - var cordovaHandler = mockControl.createStrictMock( - fireauth.CordovaHandler); - var cordovaHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'CordovaHandler'); - // Confirm expected endpoint used. - cordovaHandlerConstructor( - authDomain1, apiKey1, appName1, firebase.SDK_VERSION, undefined, - undefined, fireauth.constants.Endpoint.STAGING.id, ignoreArgument) - .$returns(cordovaHandler); - mockControl.$replayAll(); - - fireauth.AuthEventManager.instantiateOAuthSignInHandler( - authDomain1, apiKey1, appName1, firebase.SDK_VERSION, - fireauth.constants.Endpoint.STAGING.id); -} - - -/** Asserts that emulator config is propagated to cordovaHandler. */ -function testInstantiateOAuthSignInHandler_cordovaHandler_withEmulator() { - // Simulate Cordova environment - setOAuthSignInHandlerEnvironment(true); - // CordovaHandler should be instantiated. - var cordovaHandler = mockControl.createStrictMock( - fireauth.CordovaHandler); - var cordovaHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'CordovaHandler'); - var emulatorConfig = { - url: 'http://emulator.test.domain:1234' - }; - // Confirm expected endpoint used. - cordovaHandlerConstructor( - authDomain1, apiKey1, appName1, firebase.SDK_VERSION, undefined, - undefined, fireauth.constants.Endpoint.STAGING.id, emulatorConfig) - .$returns(cordovaHandler); - mockControl.$replayAll(); - - fireauth.AuthEventManager.instantiateOAuthSignInHandler( - authDomain1, apiKey1, appName1, firebase.SDK_VERSION, - fireauth.constants.Endpoint.STAGING.id, emulatorConfig); -} - - -function testAuthEventManager_initialize_manually_withSubscriber() { - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', '1234', 'http://www.example.com/#response', 'SESSION_ID'); - // This test is not environment specific. - stubs.replace( - fireauth.AuthEventManager, - 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName, version) { - assertEquals('subdomain1.firebaseapp.com', authDomain); - assertEquals('API_KEY1', apiKey); - assertEquals('APP1', appName); - assertEquals(firebase.SDK_VERSION, version); - return { - 'addAuthEventListener': function(handler) { - asyncTestCase.signal(); - // Trigger immediately to test that handleAuthEvent_ - // is triggered with expected event. - handler(expectedAuthEvent); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { - return false; - }, - 'hasVolatileStorage': function() { - return false; - } - }; - }); - var handler1 = { - 'canHandleAuthEvent': function(type, id) { - // Auth event should be passed to handler to check if it can handle it. - assertEquals('linkViaPopup', type); - assertEquals('1234', id); - asyncTestCase.signal(); - return false; - }, - 'resolvePendingPopupEvent': goog.testing.recordFunction(), - 'getAuthEventHandlerFinisher': goog.testing.recordFunction() - }; - asyncTestCase.waitForSignals(2); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.subscribe(handler1); - manager.initialize(); -} - - -function testAuthEventManager_initialize_manually_withNoSubscriber() { - // Test manual initialization with no subscriber. - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', '1234', 'http://www.example.com/#response', 'SESSION_ID'); - var isReady = false; - // This test is not environment specific. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // This should not be called twice on initialization. - assertFalse(isReady); - // Trigger expected event. - isReady = true; - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(2); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.initialize().then(function() { - assertTrue(isReady); - asyncTestCase.signal(); - }); - // This will return the above cached response and should not try to - // initialize a new handler. - manager.initialize().then(function() { - assertTrue(isReady); - asyncTestCase.signal(); - }); -} - - -function testAuthEventManager_initialize_manually_withEmulator() { - var expectedEmulatorConfig = { - url: 'http://emulator.test.domain:1234' - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', '1234', 'http://www.example.com/#response', 'SESSION_ID'); - var isReady = false; - // This test is not environment specific. - stubs.replace( - fireauth.AuthEventManager, - 'instantiateOAuthSignInHandler', - function (authDomain, apiKey, appName, version, endpoint, emulatorConfig) { - assertEquals('subdomain1.firebaseapp.com', authDomain); - assertEquals('API_KEY1', apiKey); - assertEquals('APP1', appName); - assertEquals(firebase.SDK_VERSION, version); - assertUndefined(endpoint); - assertObjectEquals(emulatorConfig, expectedEmulatorConfig); - isReady = true; - return { - 'addAuthEventListener': function (handler) { - handler(expectedAuthEvent); - }, - 'initializeAndWait': function () { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function () { - return false; - }, - 'hasVolatileStorage': function () { - return false; - } - }; - }); - asyncTestCase.waitForSignals(1); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1, expectedEmulatorConfig); - manager.initialize().then(function () { - assertTrue(isReady); - asyncTestCase.signal(); - }); -} - - -function testAuthEventManager_initialize_automatically_pendingRedirect() { - // Test automatic initialization when pending redirect available on - // subscription. - // This test is relevant to a browser environment. - setOAuthSignInHandlerEnvironment(false); - var expectedAuthEvent = new fireauth.AuthEvent( - 'signInViaRedirect', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - var isInitialized = false; - // Used to trigger the Auth event. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - isInitialized = true; - // Trigger expected event. - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(1); - var expectedResult = { - 'user': {}, - 'credential': {} - }; - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var handler1 = { - 'canHandleAuthEvent': function(type, id) { - // Auth event should be passed to handler to check if it can handle it. - assertEquals('signInViaRedirect', type); - assertEquals('1234', id); - return true; - }, - 'resolvePendingPopupEvent': function( - mode, popupRedirectResult, error, opt_eventId) { - }, - 'getAuthEventHandlerFinisher': function(mode, opt_eventId) { - return function(requestUri, sessionId, tenantId, postBody) { - return goog.Promise.resolve(expectedResult); - }; - } - }; - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - // Simulate pending result. - pendingRedirectManager.setPendingStatus().then(function() { - // This will trigger initialize due to pending redirect. - manager.subscribe(handler1); - // This will resolve with the expected result. - manager.getRedirectResult().then(function(result) { - assertTrue(isInitialized); - assertObjectEquals(expectedResult, result); - // Pending result should be cleared. - pendingRedirectManager.getPendingStatus().then(function(status) { - assertFalse(status); - asyncTestCase.signal(); - }); - }); - - }); -} - - -function testAuthEventManager_initialize_automatically_volatileStorage() { - // If interface has volatile storage, handler should be automatically - // initialized even when no pending redirect is detected. - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var expectedAuthEvent = new fireauth.AuthEvent( - 'signInViaRedirect', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - var isInitialized = false; - // Used to trigger the Auth event. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - isInitialized = true; - // Trigger expected event. - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(1); - var expectedResult = { - 'user': {}, - 'credential': {} - }; - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var handler1 = { - 'canHandleAuthEvent': function(type, id) { - // auth event should be passed to handler to check if it can handle it. - assertEquals('signInViaRedirect', type); - assertEquals('1234', id); - return true; - }, - 'resolvePendingPopupEvent': function( - mode, popupRedirectResult, error, opt_eventId) { - }, - 'getAuthEventHandlerFinisher': function(mode, opt_eventId) { - return function(requestUri, sessionId, tenantId, postBody) { - return goog.Promise.resolve(expectedResult); - }; - } - }; - // This will trigger initialize even though there is no pending redirect in - // session storage. - manager.subscribe(handler1); - // This will resolve with the expected result. - manager.getRedirectResult().then(function(result) { - assertTrue(isInitialized); - assertEquals(expectedResult, result); - asyncTestCase.signal(); - }); -} - - -function testAuthEventManager_initialize_automatically_safariMobile() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - // Test automatic initialization on subscription for Safari mobile. - var ua = 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 ' + - '(KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25'; - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return ua; - }); - // OAuth handler should be initialized automatically on subscription. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var handler = { - 'canHandleAuthEvent': goog.testing.recordFunction(), - 'resolvePendingPopupEvent': goog.testing.recordFunction(), - 'getAuthEventHandlerFinisher': goog.testing.recordFunction() - }; - manager.subscribe(handler); -} - - -function testAuthEventManager_getRedirectResult_noRedirect() { - // Browser environment where sessionStorage is not volatile. - // Test getRedirect when no pending redirect is found. - setOAuthSignInHandlerEnvironment(false); - asyncTestCase.waitForSignals(1); - stubs.replace( - fireauth.AuthEventManager.prototype, - 'initialize', - function() { - fail('This should not initialize automatically.'); - }); - var expectedResult = { - 'user': null - }; - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var handler1 = { - 'canHandleAuthEvent': goog.testing.recordFunction(), - 'resolvePendingPopupEvent': goog.testing.recordFunction(), - 'getAuthEventHandlerFinisher': goog.testing.recordFunction() - }; - // This will resolve redirect result to default null result. - manager.subscribe(handler1); - // This should resolve quickly since there is no pending redirect without the - // need to initialize the OAuth sign-in handler. - manager.getRedirectResult().then(function(result) { - assertObjectEquals(expectedResult, result); - asyncTestCase.signal(); - }); -} - - -function testAuthEventManager_subscribeAndUnsubscribe() { - var recordedHandler = null; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', '1234', 'http://www.example.com/#response', 'SESSION_ID'); - stubs.replace( - fireauth.PopupAuthEventProcessor.prototype, - 'processAuthEvent', - goog.testing.recordFunction()); - // This test is not environment specific. - stubs.replace( - fireauth.AuthEventManager, - 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName, version) { - assertEquals('subdomain1.firebaseapp.com', authDomain); - assertEquals('API_KEY1', apiKey); - assertEquals('APP1', appName); - assertEquals(firebase.SDK_VERSION, version); - return { - 'addAuthEventListener': function(handler) { - recordedHandler = handler; - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { - return false; - }, - 'hasVolatileStorage': function() { - return false; - } - }; - }); - // Create a handler that can't handle the provided event. - var handler1 = { - 'canHandleAuthEvent': goog.testing.recordFunction(function(type, eventId) { - assertEquals('linkViaPopup', type); - assertEquals('1234', eventId); - return false; - }), - 'resolvePendingPopupEvent': goog.testing.recordFunction(), - 'getAuthEventHandlerFinisher': goog.testing.recordFunction() - }; - // Create another handler that can't handle the provided event. - var handler2 = { - 'canHandleAuthEvent': goog.testing.recordFunction(function(type, eventId) { - assertEquals('linkViaPopup', type); - assertEquals('1234', eventId); - return false; - }), - 'resolvePendingPopupEvent': goog.testing.recordFunction(), - 'getAuthEventHandlerFinisher': goog.testing.recordFunction() - }; - // Create a handler that can handle a specified Auth event. - var handler3 = { - 'canHandleAuthEvent': goog.testing.recordFunction(function(type, eventId) { - assertEquals('linkViaPopup', type); - assertEquals('1234', eventId); - return true; - }), - 'resolvePendingPopupEvent': goog.testing.recordFunction(), - 'getAuthEventHandlerFinisher': goog.testing.recordFunction() - }; - asyncTestCase.waitForSignals(1); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.initialize(); - assertFalse(manager.isSubscribed(handler1)); - // Subscribe first handler and trigger event. - manager.subscribe(handler1); - assertTrue(manager.isSubscribed(handler1)); - assertFalse(recordedHandler(expectedAuthEvent)); - assertEquals(1, handler1.canHandleAuthEvent.getCallCount()); - assertEquals(0, handler2.canHandleAuthEvent.getCallCount()); - assertEquals( - 0, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Subscribe second handler and trigger event. - assertFalse(manager.isSubscribed(handler2)); - manager.subscribe(handler2); - assertTrue(manager.isSubscribed(handler2)); - assertFalse(recordedHandler(expectedAuthEvent)); - assertEquals(2, handler1.canHandleAuthEvent.getCallCount()); - assertEquals(1, handler2.canHandleAuthEvent.getCallCount()); - assertEquals( - 0, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Unsubscribe first handler and trigger event. - manager.unsubscribe(handler1); - assertFalse(manager.isSubscribed(handler1)); - assertFalse(recordedHandler(expectedAuthEvent)); - assertEquals(2, handler1.canHandleAuthEvent.getCallCount()); - assertEquals(2, handler2.canHandleAuthEvent.getCallCount()); - assertEquals( - 0, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Unsubscribe second handler and trigger event. - manager.unsubscribe(handler2); - assertFalse(manager.isSubscribed(handler2)); - assertFalse(recordedHandler(expectedAuthEvent)); - assertEquals(2, handler1.canHandleAuthEvent.getCallCount()); - assertEquals(2, handler2.canHandleAuthEvent.getCallCount()); - assertEquals( - 0, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Reset handler. - handler1.canHandleAuthEvent.reset(); - handler2.canHandleAuthEvent.reset(); - handler3.canHandleAuthEvent.reset(); - // Subscribe all handlers and add handler3. - manager.subscribe(handler1); - manager.subscribe(handler3); - manager.subscribe(handler2); - // Trigger event, once it reaches the correct handler, it will stop checking - // other handlers and return true. - assertTrue(recordedHandler(expectedAuthEvent)); - assertEquals(1, handler1.canHandleAuthEvent.getCallCount()); - assertEquals(0, handler2.canHandleAuthEvent.getCallCount()); - assertEquals(1, handler3.canHandleAuthEvent.getCallCount()); - assertEquals( - 1, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - // processAuthEvent should be called with the expected event and handler3 as - // owner of that event. - assertEquals( - expectedAuthEvent, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent.getLastCall() - .getArgument(0)); - assertEquals( - handler3, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent.getLastCall() - .getArgument(1)); -} - - -function testAuthEventManager_testEventToProcessor() { - var recordedHandler; - clock = new goog.testing.MockClock(true); - asyncTestCase.waitForSignals(1); - // This test is not environment specific. - stubs.replace( - fireauth.AuthEventManager, - 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName, version) { - assertEquals('subdomain1.firebaseapp.com', authDomain); - assertEquals('API_KEY1', apiKey); - assertEquals('APP1', appName); - assertEquals(firebase.SDK_VERSION, version); - return { - 'addAuthEventListener': function(handler) { - recordedHandler = handler; - asyncTestCase.signal(); - }, - 'removeAuthEventListener': function(handler) { - recordedHandler = null; - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { - return false; - }, - 'hasVolatileStorage': function() { - return false; - } - }; - }); - stubs.replace( - fireauth.PopupAuthEventProcessor.prototype, - 'processAuthEvent', - goog.testing.recordFunction()); - stubs.replace( - fireauth.RedirectAuthEventProcessor.prototype, - 'processAuthEvent', - goog.testing.recordFunction()); - // For testing all cases, use a handler that can handler everything. - var handler = { - 'canHandleAuthEvent': function(type, eventId) {return true;}, - 'resolvePendingPopupEvent': goog.testing.recordFunction(), - 'getAuthEventHandlerFinisher': goog.testing.recordFunction() - }; - var unknownEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var signInViaPopupEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - 'POST_BODY'); - var signInViaRedirectEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - 'POST_BODY'); - var linkViaPopupEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - 'POST_BODY'); - var linkViaRedirectEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - 'POST_BODY'); - var reauthViaPopupEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - 'POST_BODY'); - var reauthViaRedirectEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - 'POST_BODY'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.initialize(); - manager.subscribe(handler); - // Test with unknown event. - recordedHandler(unknownEvent); - assertEquals( - 1, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertArrayEquals( - [unknownEvent, handler], - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getLastCall().getArguments()); - assertEquals( - 0, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Test with sign up via popup event. - recordedHandler(signInViaPopupEvent); - assertEquals( - 1, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertEquals( - 1, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertArrayEquals( - [signInViaPopupEvent, handler], - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getLastCall().getArguments()); - - // Test with sign in via redirect event. - recordedHandler(signInViaRedirectEvent); - assertEquals( - 2, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertArrayEquals( - [signInViaRedirectEvent, handler], - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getLastCall().getArguments()); - assertEquals( - 1, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Test with link via popup event. - recordedHandler(linkViaPopupEvent); - assertEquals( - 2, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertEquals( - 2, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertArrayEquals( - [linkViaPopupEvent, handler], - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getLastCall().getArguments()); - - // Test with link via redirect event. - recordedHandler(linkViaRedirectEvent); - assertEquals( - 3, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertArrayEquals( - [linkViaRedirectEvent, handler], - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getLastCall().getArguments()); - assertEquals( - 2, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Test with reauth via popup event. - recordedHandler(reauthViaPopupEvent); - assertEquals( - 3, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertEquals( - 3, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertArrayEquals( - [reauthViaPopupEvent, handler], - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getLastCall().getArguments()); - - // Test with reauth via redirect event. - recordedHandler(reauthViaRedirectEvent); - assertEquals( - 4, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertArrayEquals( - [reauthViaRedirectEvent, handler], - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getLastCall().getArguments()); - assertEquals( - 3, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Duplicate events with event IDs or session IDs should be ignored. - assertFalse(recordedHandler(signInViaPopupEvent)); - assertFalse(recordedHandler(signInViaRedirectEvent)); - assertFalse(recordedHandler(linkViaPopupEvent)); - assertFalse(recordedHandler(linkViaRedirectEvent)); - assertFalse(recordedHandler(reauthViaPopupEvent)); - assertFalse(recordedHandler(reauthViaRedirectEvent)); - assertEquals( - 4, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertEquals( - 3, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - // Unknown events are allowed to be duplicated. - assertTrue(recordedHandler(unknownEvent)); - assertEquals( - 5, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertEquals( - 3, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - - // Reset should clear processed events. - manager.reset(); - manager.initialize(); - assertTrue(recordedHandler(signInViaPopupEvent)); - assertEquals( - 5, - fireauth.RedirectAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertEquals( - 4, - fireauth.PopupAuthEventProcessor.prototype.processAuthEvent. - getCallCount()); - assertFalse(recordedHandler(signInViaPopupEvent)); - - // Simulate 1 millisecond before cachebuster triggers. - clock.tick(fireauth.AuthEventManager.EVENT_DUPLICATION_CACHE_DURATION - 1); - // Event uid should still be saved. - assertFalse(recordedHandler(signInViaPopupEvent)); - // Simulate one more millisecond to clear cache. - clock.tick(1); - // Event uid should be cleared. - assertTrue(recordedHandler(signInViaPopupEvent)); - // This should be cached until next time cache is cleared. - clock.tick(fireauth.AuthEventManager.EVENT_DUPLICATION_CACHE_DURATION - 1); - assertFalse(recordedHandler(signInViaPopupEvent)); - clock.tick(1); - assertTrue(recordedHandler(signInViaPopupEvent)); - - // Halfway through timeout duration. - clock.tick(fireauth.AuthEventManager.EVENT_DUPLICATION_CACHE_DURATION / 2); - // Trigger second event. - assertTrue(recordedHandler(linkViaPopupEvent)); - // Halfway through timeout. - clock.tick(fireauth.AuthEventManager.EVENT_DUPLICATION_CACHE_DURATION / 2); - // Both events still cached (second event should reset the counter). - assertFalse(recordedHandler(signInViaPopupEvent)); - assertFalse(recordedHandler(linkViaPopupEvent)); - // Trigger timeout (half timeout duration). - clock.tick(fireauth.AuthEventManager.EVENT_DUPLICATION_CACHE_DURATION / 2); - // Cache should be cleared from both events (full timeout duration from last - // event). - assertTrue(recordedHandler(signInViaPopupEvent)); - assertTrue(recordedHandler(linkViaPopupEvent)); -} - - -function testProcessPopup_success() { - // This is only relevant to OAuth handlers that support popups. - setOAuthSignInHandlerEnvironment(false); - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, - apiKey1, - appName1, - 'linkViaPopup', - provider, - null, - '1234', - firebase.SDK_VERSION); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Fake popup window. - var popupWin = {}; - // Keep track of when the popup is redirected. - var popupRedirected = false; - // Catch popup window redirection. - stubs.replace( - fireauth.util, - 'goTo', - function(url, win) { - popupRedirected = true; - assertEquals(expectedUrl, url); - assertEquals(popupWin, win); - asyncTestCase.signal(); - }); - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(3); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .then(function() { - assertTrue(popupRedirected); - // This should resolve now as it is already initialized. - asyncTestCase.signal(); - }); - // Confirm OAuth handler initialized before redirect. - manager.initialize().then(function() { - // Should not be redirected yet. - assertFalse(popupRedirected); - asyncTestCase.signal(); - }); -} - - -function testProcessPopup_success_tenantId() { - // This is only relevant to OAuth handlers that support popups. - setOAuthSignInHandlerEnvironment(false); - var tenantId = '123456789012'; - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, - apiKey1, - appName1, - 'linkViaPopup', - provider, - null, - '1234', - firebase.SDK_VERSION, - null, - null, - tenantId); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Fake popup window. - var popupWin = {}; - // Keep track of when the popup is redirected. - var popupRedirected = false; - // Catch popup window redirection. - stubs.replace( - fireauth.util, - 'goTo', - function(url, win) { - popupRedirected = true; - assertEquals(expectedUrl, url); - assertEquals(popupWin, win); - asyncTestCase.signal(); - }); - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(3); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.processPopup( - popupWin, 'linkViaPopup', provider, '1234', false, tenantId) - .then(function() { - assertTrue(popupRedirected); - // This should resolve now as it is already initialized. - asyncTestCase.signal(); - }); - // Confirm OAuth handler initialized before redirect. - manager.initialize().then(function() { - // Should not be redirected yet. - assertFalse(popupRedirected); - asyncTestCase.signal(); - }); -} - - -function testProcessPopup_popupNotSupported() { - // Test for environments where popup sign in is not supported. - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - // Fake popup window. - var popupWin = {}; - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(1); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testReset_ifcHandler() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - var oauthHandlerInstance = null; - var calls = 0; - // Listener to initializations of ifchandler. - stubs.replace( - OAuthSignInHandler.prototype, - 'initializeAndWait', - goog.testing.recordFunction( - OAuthSignInHandler.prototype.initializeAndWait)); - stubs.replace( - OAuthSignInHandler.prototype, 'addAuthEventListener', - function(handler) { - // Each call should be run on a new instance as reset will force a new - // instance to be created. - assertNotEquals(oauthHandlerInstance, this); - // Save current instance. - oauthHandlerInstance = this; - calls++; - }); - var manager = - fireauth.AuthEventManager.getManager(authDomain1, apiKey1, appName1); - // This should be cancelled by reset. - manager.initialize(); - // Note first initialized ifchandler instance. - var initializedInstance1 = OAuthSignInHandler.prototype - .initializeAndWait.getLastCall().getThis(); - manager.reset(); - // This call should succeed. - manager.initialize(); - // Note second initialized ifchandler instance. - var initializedInstance2 = OAuthSignInHandler.prototype - .initializeAndWait.getLastCall().getThis(); - // Confirm both instances are not the same. - assertNotEquals(initializedInstance1, initializedInstance2); - assertEquals(2, calls); -} - - -function testReset_cordovaHandler() { - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var oauthHandlerInstance = null; - var calls = 0; - // Listener to initializations of the current OAuth sign in handler. - stubs.replace( - OAuthSignInHandler.prototype, - 'initializeAndWait', - goog.testing.recordFunction( - OAuthSignInHandler.prototype.initializeAndWait)); - stubs.replace( - OAuthSignInHandler.prototype, 'addAuthEventListener', - function(handler) { - // Each call should be run on a new instance as reset will force a new - // instance to be created. - assertNotEquals(oauthHandlerInstance, this); - // Save current instance. - oauthHandlerInstance = this; - calls++; - }); - var manager = - fireauth.AuthEventManager.getManager(authDomain1, apiKey1, appName1); - // This should be cancelled by reset. - manager.initialize(); - // Note first initialized ifchandler instance. - var initializedInstance1 = OAuthSignInHandler.prototype - .initializeAndWait.getLastCall().getThis(); - manager.reset(); - // This call should succeed. - manager.initialize(); - // Note second initialized ifchandler instance. - var initializedInstance2 = OAuthSignInHandler.prototype - .initializeAndWait.getLastCall().getThis(); - // Confirm both instances are not the same. - assertNotEquals(initializedInstance1, initializedInstance2); - assertEquals(2, calls); -} - - -function testInitialize_errorLoadingOAuthHandler() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - // Test manager initialization when the OAuth handler fails to load. - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', null, null, null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Listen to reset calls. - stubs.replace( - fireauth.AuthEventManager.prototype, 'reset', - goog.testing.recordFunction(fireauth.AuthEventManager.prototype.reset)); - // If the OAuth handler is to be initialized, trigger no redirect event to - // notify event manager that it is ready. - stubs.replace( - OAuthSignInHandler.prototype, 'addAuthEventListener', - function(handler) { - // Only when handler is not failing, handle event. - if (!willFail) { - handler(expectedAuthEvent); - } - }); - stubs.replace( - OAuthSignInHandler.prototype, - 'initializeAndWait', - function() { - if (willFail) { - // When handler fails to load. - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED)); - } - // Handler succeeds to load. - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - var manager = - fireauth.AuthEventManager.getManager(authDomain1, apiKey1, appName1); - // Simulate the handler first fails to load. - var willFail = true; - // Reset not called yet. - assertEquals(0, fireauth.AuthEventManager.prototype.reset.getCallCount()); - manager.initialize().thenCatch(function(error) { - // OAuth handler should be reset. - assertEquals(1, fireauth.AuthEventManager.prototype.reset.getCallCount()); - // Network error triggered. - assertErrorEquals(expectedError, error); - // Simulate on next trial the handler will load correctly. - willFail = false; - // Try to initialize again. This time it should work. - manager.initialize().then(function() { - // No additional call to reset. - assertEquals(1, fireauth.AuthEventManager.prototype.reset.getCallCount()); - // This should resolve now as it is already initialized. - asyncTestCase.signal(); - }); - }); -} - - -function testProcessPopup_errorLoadingIframe() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - // Test when the handler fails to load. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, apiKey1, appName1, 'linkViaPopup', provider, null, '1234', - firebase.SDK_VERSION); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', null, null, null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Fake popup window. - var popupWin = {}; - var popupRedirected = false; - // Listen to reset calls. - stubs.replace( - fireauth.AuthEventManager.prototype, 'reset', - goog.testing.recordFunction(fireauth.AuthEventManager.prototype.reset)); - stubs.replace( - fireauth.RpcHandler.prototype, 'getAuthorizedDomains', function() { - // Assume connection works for this call and only fails on handler load. - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - // Catch popup window redirection. - stubs.replace(fireauth.util, 'goTo', function(url, win) { - if (willFail) { - // When handler fails, no redirect should happen. - fail('OAuth handler loading failure should not lead to a popup ' + - 'redirect.'); - } else { - // When OAuth handler succeeds, it should redirect the popup window. - popupRedirected = true; - assertEquals(expectedUrl, url); - assertEquals(popupWin, win); - } - }); - // If the OAuth handler is to be initialized, trigger no redirect event to - // notify event manager that it is ready. - stubs.replace( - OAuthSignInHandler.prototype, 'addAuthEventListener', - function(handler) { - // Only when handler is not failing, handle event. - if (!willFail) { - handler(expectedAuthEvent); - } - }); - stubs.replace( - OAuthSignInHandler.prototype, - 'initializeAndWait', - function() { - if (willFail) { - // When handler fails to load. - return goog.Promise.reject(new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED)); - } - // Handler succeeds to load. - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - var manager = - fireauth.AuthEventManager.getManager(authDomain1, apiKey1, appName1); - // Simulate the OAuth handler first fails to load. - var willFail = true; - // Reset not called yet. - assertEquals(0, fireauth.AuthEventManager.prototype.reset.getCallCount()); - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .thenCatch(function(error) { - // OAuth handler should be reset. - assertEquals( - 1, fireauth.AuthEventManager.prototype.reset.getCallCount()); - // No redirect. - assertFalse(popupRedirected); - // Network error triggered. - assertErrorEquals(expectedError, error); - // Simulate on next trial the OAuth handler will load correctly. - willFail = false; - // This will succeed now. - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .then(function() { - // No additional call to reset. - assertEquals( - 1, fireauth.AuthEventManager.prototype.reset.getCallCount()); - assertTrue(popupRedirected); - // This should resolve now as it is already initialized. - asyncTestCase.signal(); - }); - }); -} - - -function testProcessPopup_getAuthorizedDomainsNetworkError() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - // Test when getAuthorizedDomains throws a network error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, apiKey1, appName1, 'linkViaPopup', provider, null, '1234', - firebase.SDK_VERSION); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', null, null, null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Fake popup window. - var popupWin = {}; - var popupRedirected = false; - // Listen to reset calls. - stubs.replace( - fireauth.AuthEventManager.prototype, 'reset', - goog.testing.recordFunction(fireauth.AuthEventManager.prototype.reset)); - stubs.replace( - fireauth.RpcHandler.prototype, 'getAuthorizedDomains', function() { - // Throw network error when this is supposed to fail. - if (willFail) { - return goog.Promise.reject(expectedError); - } - // Assume connection works for this call and only fails on handler load. - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - // Catch popup window redirection. - stubs.replace(fireauth.util, 'goTo', function(url, win) { - if (willFail) { - // When OAuth handler fails, no redirect should happen. - fail('OAuth handler loading failure should not lead to a popup ' + - 'redirect.'); - } else { - // When OAuth handler succeeds, it should redirect the popup window. - popupRedirected = true; - assertEquals(expectedUrl, url); - assertEquals(popupWin, win); - } - }); - // If the OAuth handler is to be initialized, trigger no redirect event to - // notify event manager that it is ready. - stubs.replace( - OAuthSignInHandler.prototype, 'addAuthEventListener', - function(handler) { - // This should not be called since the handler should not be initialized - // in this case. - if (willFail) { - fail('getAuthorizedDomains error should not trigger handler init.'); - } - // Only when OAuth handler is not failing, handle event. - handler(expectedAuthEvent); - }); - stubs.replace( - OAuthSignInHandler.prototype, - 'initializeAndWait', - function() { - // This should not be called since the OAuth handler should not be - // initialized in this case. - if (willFail) { - fail('getAuthorizedDomains error should not trigger handler init.'); - } - // OAuth handler succeeds to load. - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - var manager = - fireauth.AuthEventManager.getManager(authDomain1, apiKey1, appName1); - // Simulate getAuthorizedDomains network error. - var willFail = true; - // Reset not called. - assertEquals(0, fireauth.AuthEventManager.prototype.reset.getCallCount()); - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .thenCatch(function(error) { - // No reset needed as the OAuth handler was not initialized to begin - // with. - assertEquals( - 0, fireauth.AuthEventManager.prototype.reset.getCallCount()); - // No redirect. - assertFalse(popupRedirected); - // Network error triggered. - assertErrorEquals(expectedError, error); - // Simulate on next trial the OAuth handler will load correctly and - // getAuthorizedDomains would resolve correctly. - willFail = false; - // This will succeed now. - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .then(function() { - // No call to reset. - assertEquals( - 0, fireauth.AuthEventManager.prototype.reset.getCallCount()); - // Popup redirected successfully - assertTrue(popupRedirected); - // This should resolve now as it is already initialized. - asyncTestCase.signal(); - }); - }); -} - - -function testProcessPopup_alreadyRedirected() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - // Test processPopup when it's already redirected. This is used in mobile - // environments. - // Fake popup window. - var popupWin = {}; - // OAuth handler should be initialized automatically if not already - // initialized. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(2); - var provider = new fireauth.GoogleAuthProvider(); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234', true) - .then(function() { - // This should resolve immediately. - asyncTestCase.signal(); - }); -} - - -function testProcessPopup_success_confirmAutoInitialized() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - // Test initialize called automatically when processPopup called. - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, - apiKey1, - appName1, - 'linkViaPopup', - provider, - null, - '1234', - firebase.SDK_VERSION); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Fake popup window. - var popupWin = {}; - // Catch popup window redirection. - stubs.replace( - fireauth.util, - 'goTo', - function(url, win) { - assertEquals(expectedUrl, url); - assertEquals(popupWin, win); - asyncTestCase.signal(); - }); - // If the OAuth handler is to be initialized, trigger no redirect event to - // notify event manager that it is ready. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(2); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .then(function() { - // This should resolve immediately as it is already initialized. - manager.initialize().then(function() { - asyncTestCase.signal(); - }); - }); -} - - -function testProcessPopup_error_invalidOrigin() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - // Simulate when popup is requested with invalid origin. - // Expected invalid origin error. - var expectedError = - new fireauth.InvalidOriginError(fireauth.util.getCurrentUrl()); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // If the OAuth handler is to be initialized, trigger no redirect event. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - // Assume origin is an invalid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - // No authorized domains. - return goog.Promise.resolve([]); - }); - // Fake popup window. - var popupWin = {}; - asyncTestCase.waitForSignals(2); - var provider = new fireauth.GoogleAuthProvider(); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - // This should fail with invalid origin error. - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // processPopup should initialize manager regardless of error. - manager.initialize().then(function() { - asyncTestCase.signal(); - }); -} - - -function testProcessPopup_error_blockedPopup() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // If the OAuth handler is to be initialized, trigger no redirect event. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(2); - var provider = new fireauth.GoogleAuthProvider(); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.processPopup(null, 'linkViaPopup', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.POPUP_BLOCKED), - error); - asyncTestCase.signal(); - }); - // processPopup should initialize manager regardless of error. - manager.initialize().then(function() { - asyncTestCase.signal(); - }); -} - - -function testProcessPopup_error_unsupportedProvider() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // If the OAuth handler is to be initialized, trigger no redirect event. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - asyncTestCase.waitForSignals(2); - // Fake popup window. - var popupWin = {}; - var provider = new fireauth.EmailAuthProvider(); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OAUTH_PROVIDER), - error); - asyncTestCase.signal(); - }); - // processPopup should initialize manager regardless of error. - manager.initialize().then(function() { - asyncTestCase.signal(); - }); -} - - -function testProcessRedirect_success_ifchandler() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, - apiKey1, - appName1, - 'linkViaRedirect', - provider, - window.location.href, - '1234', - firebase.SDK_VERSION); - stubs.replace( - fireauth.util, - 'goTo', - function(url) { - assertEquals(expectedUrl, url); - // Pending redirect should be saved. - pendingRedirectManager.getPendingStatus().then(function(status) { - assertTrue(status); - asyncTestCase.signal(); - }); - }); - asyncTestCase.waitForSignals(1); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - manager.processRedirect('linkViaRedirect', provider, '1234'); -} - - -function testProcessRedirect_success_ifchandler_tenantId() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - var tenantId = '123456789012'; - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, - apiKey1, - appName1, - 'linkViaRedirect', - provider, - window.location.href, - '1234', - firebase.SDK_VERSION, - null, - null, - tenantId); - stubs.replace( - fireauth.util, - 'goTo', - function(url) { - assertEquals(expectedUrl, url); - // Pending redirect should be saved. - pendingRedirectManager.getPendingStatus().then(function(status) { - assertTrue(status); - asyncTestCase.signal(); - }); - }); - asyncTestCase.waitForSignals(1); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - manager.processRedirect('linkViaRedirect', provider, '1234', tenantId); -} - - -function testAuthEventManager_nonCordovaIosOrAndroidFileEnvironment() { - // Simulate Android file browser environment. - setOAuthSignInHandlerEnvironment(false); - stubs.replace( - fireauth.util, - 'isAndroidOrIosCordovaScheme', - function() { - return true; - }); - stubs.replace( - fireauth.util, - 'checkIfCordova', - function() { - return goog.Promise.reject(); - }); - var popupWin = { - closed: false - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - var provider = new fireauth.GoogleAuthProvider(); - handler.canHandleAuthEvent = function(mode, opt_eventId) { - return true; - }; - asyncTestCase.waitForSignals(4); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.subscribe(handler); - // All popup/redirect methods should fail with operation not supported errors. - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // Clear redirect result will not clear an operation not supported error. - manager.clearRedirectResult(); - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - }); - manager.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - manager.processPopup(popupWin, 'linkViaPopup', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - manager.startPopupTimeout(handler, 'linkViaPopup', popupWin, '1234') - .then(function() { - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertErrorEquals( - expectedError, - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - asyncTestCase.signal(); - }); -} - - -function testProcessRedirect_success_cordovahandler() { - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var rawSessionId = '11111111111111111111'; - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, - apiKey1, - appName1, - 'linkViaRedirect', - provider, - null, - '1234', - firebase.SDK_VERSION, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256(rawSessionId) - }); - var pendingRedirectError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_OPERATION_PENDING); - var savedCb = null; - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - cordova.plugins.browsertab.openUrl = function(url) { - assertEquals(expectedUrl, url); - savedCb({url: incomingUrl}); - }; - universalLinks.subscribe = function(eventName, cb) { - // Trigger initial no event. - cb({url: null}); - savedCb = cb; - }; - // Simulate handler can handle the event. - handler.canHandleAuthEvent = function(mode, opt_eventId) { - return true; - }; - handler.getAuthEventHandlerFinisher = function(mode, opt_eventId) { - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals(incomingUrl, requestUri); - assertEquals(sessionId, rawSessionId); - // postBody not supported in Cordova flow. - assertNull(postBody); - assertNull(tenantId); - return goog.Promise.resolve(expectedResult); - }; - }; - asyncTestCase.waitForSignals(3); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - var expectedResult = { - 'user': {}, - 'credential': {} - }; - manager.subscribe(handler); - // Initial result is null. - manager.getRedirectResult().then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); - manager.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Pending redirect should be cleared on redirect back to app. - return pendingRedirectManager.getPendingStatus(); - }).then(function(status) { - assertFalse(status); - manager.getRedirectResult().then(function(result) { - assertEquals(expectedResult, result); - // Call processRedirect again. This should resolve as there is no - // pending operation. - return manager.processRedirect('linkViaRedirect', provider, '1234'); - }).then(function() { - asyncTestCase.signal(); - }); - }); - // This should fail as the above is still pending. - manager.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(pendingRedirectError, error); - asyncTestCase.signal(); - }); -} - - -function testProcessRedirect_success_cordovahandler_tenantId() { - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var expectedTenantId = 'TENANT_ID'; - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var rawSessionId = '11111111111111111111'; - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, - apiKey1, - appName1, - 'linkViaRedirect', - provider, - null, - '1234', - firebase.SDK_VERSION, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256(rawSessionId) - }, - null, - expectedTenantId); - var pendingRedirectError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_OPERATION_PENDING); - var savedCb = null; - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - cordova.plugins.browsertab.openUrl = function(url) { - assertEquals(expectedUrl, url); - savedCb({url: incomingUrl}); - }; - universalLinks.subscribe = function(eventName, cb) { - // Trigger initial no event. - cb({url: null}); - savedCb = cb; - }; - // Simulate handler can handle the event. - handler.canHandleAuthEvent = function(mode, opt_eventId) { - return true; - }; - handler.getAuthEventHandlerFinisher = function(mode, opt_eventId) { - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals(incomingUrl, requestUri); - assertEquals(sessionId, rawSessionId); - // postBody not supported in Cordova flow. - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - return goog.Promise.resolve(expectedResult); - }; - }; - asyncTestCase.waitForSignals(3); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - var expectedResult = { - 'user': {}, - 'credential': {} - }; - manager.subscribe(handler); - // Initial result is null. - manager.getRedirectResult().then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); - manager.processRedirect('linkViaRedirect', provider, '1234', expectedTenantId) - .then(function() { - // Pending redirect should be cleared on redirect back to app. - return pendingRedirectManager.getPendingStatus(); - }).then(function(status) { - assertFalse(status); - manager.getRedirectResult().then(function(result) { - assertEquals(expectedResult, result); - // Call processRedirect again. This should resolve as there is no - // pending operation. - return manager.processRedirect( - 'linkViaRedirect', provider, '1234', expectedTenantId); - }).then(function() { - asyncTestCase.signal(); - }); - }); - // This should fail as the above is still pending. - manager.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(pendingRedirectError, error); - asyncTestCase.signal(); - }); -} - - -function testProcessRedirect_error_cordovahandler() { - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - var rawSessionId = '11111111111111111111'; - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain1, - apiKey1, - appName1, - 'linkViaRedirect', - provider, - null, - '1234', - firebase.SDK_VERSION, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256(rawSessionId) - }); - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var savedCb = null; - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback?firebaseError=' + - JSON.stringify(expectedError.toPlainObject()); - cordova.plugins.browsertab.openUrl = function(url) { - assertEquals(expectedUrl, url); - savedCb({url: incomingUrl}); - }; - universalLinks.subscribe = function(eventName, cb) { - // Trigger initial no event. - cb({url: null}); - savedCb = cb; - }; - // Simulate handler can handle the event. - handler.canHandleAuthEvent = function(mode, opt_eventId) { - return true; - }; - asyncTestCase.waitForSignals(2); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - manager.subscribe(handler); - // Initial result is null. - manager.getRedirectResult().then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); - manager.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Pending redirect should be cleared on redirect back to app. - return pendingRedirectManager.getPendingStatus(); - }).then(function(status) { - assertFalse(status); - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // Clear redirect result should clear recoverable errors. - manager.clearRedirectResult(); - manager.getRedirectResult().then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testProcessRedirect_error_unsupportedProvider_cordovaHandler() { - asyncTestCase.waitForSignals(2); - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var invalidProviderError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - var pendingRedirectError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_OPERATION_PENDING); - var provider = new fireauth.EmailAuthProvider(); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - manager.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(invalidProviderError, error); - // Pending redirect should not be saved. - return pendingRedirectManager.getPendingStatus(); - }).then(function(status) { - assertFalse(status); - // Call again, this should be allowed to complete. - return manager.processRedirect('linkViaRedirect', provider, '1234'); - }).thenCatch(function(error) { - // The expected invalid provider error is thrown. - assertErrorEquals(invalidProviderError, error); - asyncTestCase.signal(); - }); - // This should fail as there is a pending redirect operation. - manager.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(pendingRedirectError, error); - asyncTestCase.signal(); - }); -} - - -function testProcessRedirect_error_redirectCancelled_cordovaHandler() { - asyncTestCase.waitForSignals(1); - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_CANCELLED_BY_USER); - // Simulate the OAuth sign in handler throws the expected error. - stubs.replace( - OAuthSignInHandler.prototype, - 'processRedirect', - function(handler) { - // Trigger expected error. - return goog.Promise.reject(expectedError); - }); - var provider = new fireauth.GoogleAuthProvider(); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - manager.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - // The underlying OAuth handler processRedirect error should be thrown. - assertErrorEquals(expectedError, error); - // Pending redirect should not be saved. - return pendingRedirectManager.getPendingStatus(); - }).then(function(status) { - assertFalse(status); - asyncTestCase.signal(); - }); -} - - -function testGetRedirectResult_success_cordovahandler() { - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var rawSessionId = '11111111111111111111'; - // This should have been previously saved in a process redirect call. - var partialEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - rawSessionId, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - universalLinks.subscribe = function(eventName, cb) { - // Trigger initial event. - cb({url: incomingUrl}); - }; - // Simulate handler can handle the event. - handler.canHandleAuthEvent = function(mode, opt_eventId) { - assertEquals('linkViaRedirect', mode); - assertEquals('1234', opt_eventId); - return true; - }; - handler.getAuthEventHandlerFinisher = function(mode, opt_eventId) { - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals(incomingUrl, requestUri); - assertEquals(sessionId, rawSessionId); - // postBody not supported in Cordova flow. - assertNull(postBody); - assertNull(tenantId); - return goog.Promise.resolve(expectedResult); - }; - }; - var storageKey = apiKey1 + ':' + appName1; - var expectedResult = { - 'user': {}, - 'credential': {} - }; - asyncTestCase.waitForSignals(1); - // Assume pending redirect event. - savePartialEventManager.setAuthEvent(storageKey, partialEvent) - .then(function() { - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.subscribe(handler); - // Initial expected result should resolve. - manager.getRedirectResult().then(function(result) { - assertEquals(expectedResult, result); - // Clear redirect result should clear successful results. - manager.clearRedirectResult(); - manager.getRedirectResult().then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testGetRedirectResult_error_cordovahandler() { - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - var rawSessionId = '11111111111111111111'; - // This should have been previously saved in a process redirect call. - var partialEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - rawSessionId, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback?firebaseError=' + - JSON.stringify(expectedError.toPlainObject()); - universalLinks.subscribe = function(eventName, cb) { - // Trigger initial event. - cb({url: incomingUrl}); - }; - // Simulate handler can handle the event. - handler.canHandleAuthEvent = function(mode, opt_eventId) { - assertEquals('linkViaRedirect', mode); - assertEquals('1234', opt_eventId); - return true; - }; - var storageKey = apiKey1 + ':' + appName1; - asyncTestCase.waitForSignals(1); - // Assume pending redirect event. - savePartialEventManager.setAuthEvent(storageKey, partialEvent) - .then(function() { - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.subscribe(handler); - // Initial expected result should resolve. - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // Clear redirect result should clear recoverable errors. - manager.clearRedirectResult(); - manager.getRedirectResult().then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testProcessRedirect_error_invalidOrigin() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - // Simulate when redirect is requested with invalid origin. - // Expected invalid origin error. - var expectedError = - new fireauth.InvalidOriginError(fireauth.util.getCurrentUrl()); - // Assume origin is an invalid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - // No authorized domains. - return goog.Promise.resolve([]); - }); - asyncTestCase.waitForSignals(1); - var provider = new fireauth.GoogleAuthProvider(); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - // This should fail with invalid origin error. - manager.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // Pending redirect should not be saved. - return pendingRedirectManager.getPendingStatus(); - }).then(function(status) { - assertFalse(status); - asyncTestCase.signal(); - }); -} - - -function testProcessRedirect_error_unsupportedProvider_ifcHandler() { - asyncTestCase.waitForSignals(1); - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - var provider = new fireauth.EmailAuthProvider(); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - manager.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OAUTH_PROVIDER), - error); - // Pending redirect should not be saved. - return pendingRedirectManager.getPendingStatus(); - }).then(function(status) { - assertFalse(status); - asyncTestCase.signal(); - }); -} - - -function testStartPopupTimeout_webStorageSupported() { - // Browser only environment. - clock = new goog.testing.MockClock(true); - setOAuthSignInHandlerEnvironment(false); - asyncTestCase.waitForSignals(1); - // No auth event used to resolve redirect result promise. - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // If the OAuth handler is to be initialized, trigger no redirect event. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - // On OAuth handler ready, simulate web storage supported. - stubs.replace( - fireauth.iframeclient.IfcHandler.prototype, - 'isWebStorageSupported', - function() { - return goog.Promise.resolve(true); - }); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.POPUP_CLOSED_BY_USER); - var popupWin = {'closed': true}; - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - // Manager must be initialized for the OAuth handler to be ready before - // listening to the popup being closed. - manager.initialize(); - manager.startPopupTimeout( - handler, 'linkViaPopup', popupWin, '1234').then(function() { - // On timeout, resolve pending popup event should reject with a timeout - // error. - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertEquals( - null, - handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertErrorEquals( - expectedError, - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', - handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); - // Timeout popup quickly. - clock.tick(5000); -} - - -function testStartPopupTimeout_webStorageNotSupported() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - asyncTestCase.waitForSignals(1); - // No Auth event used to resolve redirect result promise. - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // If the OAuth handler is to be initialized, trigger no redirect event. - stubs.replace( - OAuthSignInHandler.prototype, - 'addAuthEventListener', - function(handler) { - // Trigger expected event. - handler(expectedAuthEvent); - }); - // On handler ready, simulate web storage not supported in iframe. - stubs.replace( - fireauth.iframeclient.IfcHandler.prototype, - 'isWebStorageSupported', - function() { - return goog.Promise.resolve(false); - }); - // Web storage not supported in iframe error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - var popupWin = { - 'closed': false, - 'close': function() { - popupWin.closed = true; - } - }; - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - // Manager must be initialized before listening to the popup being closed. - manager.initialize(); - manager.startPopupTimeout( - handler, 'linkViaPopup', popupWin, '1234').then(function() { - // On timeout, resolve pending popup event should reject with a web storage - // not supported error. - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertEquals( - null, - handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertErrorEquals( - expectedError, - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', - handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); -} - - -function testStartPopupTimeout_popupSupported_cancel() { - // Browser only environment. - clock = new goog.testing.MockClock(true); - setOAuthSignInHandlerEnvironment(false); - var popupWin = {'closed': true}; - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.startPopupTimeout( - handler, 'linkViaPopup', popupWin, '1234').then(function() { - fail('Popup timeout should be cancelled before timing out.'); - }).cancel(); -} - - -function testStartPopupTimeout_popupNotSupported() { - // Cordova environment. - setOAuthSignInHandlerEnvironment(true); - asyncTestCase.waitForSignals(1); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - var popupWin = {'closed': false}; - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.startPopupTimeout( - handler, 'linkViaPopup', popupWin, '1234').then(function() { - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertNull(handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertErrorEquals( - expectedError, - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_invalidAuthEvent() { - asyncTestCase.waitForSignals(1); - var expectedAuthEvent = null; - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler).thenCatch(function(error) { - assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT), - error); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_unknownAuthEvent() { - asyncTestCase.waitForSignals(2); - var expectedResult = { - 'user': null - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().then(function(result) { - assertObjectEquals(expectedResult, result); - asyncTestCase.signal(); - }); - manager.getRedirectAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler).then(function() { - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_unknownAuthEvent_webStorageNotSupported() { - // Browser only environment. - setOAuthSignInHandlerEnvironment(false); - asyncTestCase.waitForSignals(1); - // Web storage not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - var expectedAuthEvent = new fireauth.AuthEvent( - 'unknown', - null, - null, - null, - expectedError); - // If the OAuth handler is to be initialized, trigger unknown event with web - // storage not supported error. - stubs.replace( - fireauth.AuthEventManager, - 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName, version) { - assertEquals('subdomain1.firebaseapp.com', authDomain); - assertEquals('API_KEY1', apiKey); - assertEquals('APP1', appName); - assertEquals(firebase.SDK_VERSION, version); - return { - 'addAuthEventListener': function(handler) { - handler(expectedAuthEvent); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { - return false; - }, - 'hasVolatileStorage': function() { - return false; - } - }; - }); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - var storageKey = apiKey1 + ':' + appName1; - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(storageKey); - // Simulate handler can handle the event. - handler.canHandleAuthEvent = function(mode, opt_eventId) { - return true; - }; - // Simulate pending result. - pendingRedirectManager.setPendingStatus().then(function() { - manager.subscribe(handler); - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // Unrecoverable errors like unsupported web storage should not be - // cleared. - manager.clearRedirectResult(); - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testProcessAuthEvent_popupErrorAuthEvent() { - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', - '1234', - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getPopupAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Resolve popup with error. - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertEquals( - null, - handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertErrorEquals( - expectedError, - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', - handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_redirectErrorAuthEvent() { - asyncTestCase.waitForSignals(2); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - manager.getRedirectAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Should not be called as event is not a popup type. - assertEquals(0, handler.resolvePendingPopupEvent.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_successfulPopupAuthEvent_noPostBody() { - // Browser only environment. - asyncTestCase.waitForSignals(1); - var expectedPopupResponse = { - 'user': {}, - 'credential': {} - }; - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaPopup', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - return goog.Promise.resolve(expectedPopupResponse); - }; - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.popupAuthEventProcessor_.processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Resolve popup with success. - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertObjectEquals( - expectedPopupResponse, - handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertNull( - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', - handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_successfulPopupAuthEvent_withPostBody() { - // Browser only environment. - asyncTestCase.waitForSignals(1); - var expectedPopupResponse = { - 'user': {}, - 'credential': {} - }; - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaPopup', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - return goog.Promise.resolve(expectedPopupResponse); - }; - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', - '1234', - 'http://www.example.com/callback', - 'SESSION_ID', - null, - // Set POST body to Auth event. - 'POST_BODY'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.popupAuthEventProcessor_.processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Resolve popup with success. - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertObjectEquals( - expectedPopupResponse, - handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertNull( - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', - handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_successfulPopupAuthEvent_tenantId() { - // Verify that tenant ID in Auth event is correctly passed to the popup event - // handler. - // Browser only environment. - asyncTestCase.waitForSignals(1); - var expectedPopupResponse = { - 'user': {}, - 'credential': {} - }; - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaPopup', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals('TENANT_ID', tenantId); - return goog.Promise.resolve(expectedPopupResponse); - }; - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - 'TENANT_ID'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getPopupAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Resolve popup with success. - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertObjectEquals( - expectedPopupResponse, - handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertNull( - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', - handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_errorPopupAuthEvent_noPostBody() { - // Browser only environment. - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaPopup', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - return goog.Promise.reject(expectedError); - }; - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.popupAuthEventProcessor_.processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Resolve popup with error. - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertObjectEquals( - null, - handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertErrorEquals( - expectedError, - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', - handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_errorPopupAuthEvent_postBody() { - // Browser only environment. - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaPopup', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - return goog.Promise.reject(expectedError); - }; - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', - '1234', - 'http://www.example.com/callback', - 'SESSION_ID', - null, - // Set POST body to Auth event. - 'POST_BODY'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.popupAuthEventProcessor_.processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Resolve popup with error. - assertEquals(1, handler.resolvePendingPopupEvent.getCallCount()); - assertEquals( - 'linkViaPopup', - handler.resolvePendingPopupEvent.getLastCall().getArgument(0)); - assertObjectEquals( - null, - handler.resolvePendingPopupEvent.getLastCall().getArgument(1)); - assertErrorEquals( - expectedError, - handler.resolvePendingPopupEvent.getLastCall().getArgument(2)); - assertEquals( - '1234', - handler.resolvePendingPopupEvent.getLastCall().getArgument(3)); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_successfulRedirect_noPostBody() { - clock = new goog.testing.MockClock(true); - asyncTestCase.waitForSignals(2); - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaRedirect', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // Once the handler is called, the redirect event cannot timeout. - clock.tick(timeoutDelay); - return goog.Promise.resolve(expectedRedirectResult); - }; - }; - var expectedRedirectResult = { - 'user': {}, - 'credential': {} - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().then(function(result) { - assertObjectEquals(expectedRedirectResult, result); - asyncTestCase.signal(); - }); - manager.redirectAuthEventProcessor_.processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Popup resolve should not be called as this is not a popup event. - assertEquals(0, handler.resolvePendingPopupEvent.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_successfulRedirectAuthEvent_postBody() { - clock = new goog.testing.MockClock(true); - asyncTestCase.waitForSignals(2); - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaRedirect', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - // Once the handler is called, the redirect event cannot timeout. - clock.tick(timeoutDelay); - return goog.Promise.resolve(expectedRedirectResult); - }; - }; - var expectedRedirectResult = { - 'user': {}, - 'credential': {} - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://www.example.com/callback', - 'SESSION_ID', - null, - // Set POST body to Auth event. - 'POST_BODY'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().then(function(result) { - assertObjectEquals(expectedRedirectResult, result); - asyncTestCase.signal(); - }); - manager.redirectAuthEventProcessor_.processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Popup resolve should not be called as this is not a popup event. - assertEquals(0, handler.resolvePendingPopupEvent.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_successfulRedirect_tenantId() { - // Verify that tenant ID in Auth event is correctly passed to the redirect - // event handler. - clock = new goog.testing.MockClock(true); - asyncTestCase.waitForSignals(2); - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaRedirect', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals('TENANT_ID', tenantId); - // Once the handler is called, the redirect event cannot timeout. - clock.tick(timeoutDelay); - return goog.Promise.resolve(expectedRedirectResult); - }; - }; - var expectedRedirectResult = { - 'user': {}, - 'credential': {} - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - 'TENANT_ID'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().then(function(result) { - assertObjectEquals(expectedRedirectResult, result); - // Successful redirect result should be cleared. - manager.clearRedirectResult(); - manager.getRedirectResult().then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); - }); - manager.getRedirectAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Popup resolve should not be called as this is not a popup event. - assertEquals(0, handler.resolvePendingPopupEvent.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_errorRedirectAuthEvent_noPostBody() { - asyncTestCase.waitForSignals(2); - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaRedirect', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - return goog.Promise.reject(expectedError); - }; - }; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - manager.redirectAuthEventProcessor_.processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Popup resolve should not be called as this is not a popup event. - assertEquals(0, handler.resolvePendingPopupEvent.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_finisher_errorRedirectAuthEvent_postBody() { - asyncTestCase.waitForSignals(2); - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - assertEquals('linkViaRedirect', mode); - assertEquals('1234', eventId); - return function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - return goog.Promise.reject(expectedError); - }; - }; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://www.example.com/callback', - 'SESSION_ID', - null, - // Set POST body to Auth event. - 'POST_BODY'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - manager.redirectAuthEventProcessor_.processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Popup resolve should not be called as this is not a popup event. - assertEquals(0, handler.resolvePendingPopupEvent.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testProcessAuthEvent_noHandler() { - // No handler available for whatever reason. - handler.getAuthEventHandlerFinisher = function() {return null;}; - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH_EVENT); - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler).thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testGetRedirect_noHandlerCanProcessEvent() { - asyncTestCase.waitForSignals(2); - var expectedRedirectResult = { - 'user': null - }; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaPopup', '1234', 'http://www.example.com/#response', 'SESSION_ID'); - // This test is not environment specific. - stubs.replace( - fireauth.AuthEventManager, - 'instantiateOAuthSignInHandler', - function(authDomain, apiKey, appName, version) { - assertEquals('subdomain1.firebaseapp.com', authDomain); - assertEquals('API_KEY1', apiKey); - assertEquals('APP1', appName); - assertEquals(firebase.SDK_VERSION, version); - return { - 'addAuthEventListener': function(handler) { - // handleAuthEvent_ should not resolve. - assertFalse(handler(expectedAuthEvent)); - asyncTestCase.signal(); - }, - 'initializeAndWait': function() { return goog.Promise.resolve(); }, - 'shouldBeInitializedEarly': function() { - return false; - }, - 'hasVolatileStorage': function() { - return false; - } - }; - }); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - handler.canHandleAuthEvent = function() {return false;}; - manager.initialize(); - manager.subscribe(handler); - // When on load no handler can process event, getRedirectResult should still - // resolve. - manager.getRedirectResult().then(function(result) { - assertObjectEquals(expectedRedirectResult, result); - asyncTestCase.signal(); - }); -} - - -function testRedirectResult_timeout() { - clock = new goog.testing.MockClock(true); - asyncTestCase.waitForSignals(1); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.TIMEOUT); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // Redirect results with recoverable errors should be cleared. - manager.clearRedirectResult(); - manager.getRedirectResult().then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); - }); - // Speed up timeout. - clock.tick(timeoutDelay); -} - - -function testRedirectResult_overwritePreviousRedirectResult() { - // Once redirect result is determined, it can still be overwritten, though - // in some cases like ifchandler which gets redirect result from - // sessionStorage, this should not happen. - // Test also that clearRedirectResult will clear the cached result as long as - // the operation is not pending. - asyncTestCase.waitForSignals(2); - handler.getAuthEventHandlerFinisher = function(mode, eventId) { - return function(requestUri, sessionId, tenantId, postBody) { - // Iterate through results array each call. - return goog.Promise.resolve(results[index++]); - }; - }; - var index = 0; - var expectedRedirectResult = { - 'user': {'uid': '1234'}, - 'credential': {} - }; - var expectedRedirectResult2 = { - 'user': {'uid': '5678'}, - 'credential': {} - }; - var results = [expectedRedirectResult, expectedRedirectResult2]; - var expectedAuthEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - var manager = fireauth.AuthEventManager.getManager( - authDomain1, apiKey1, appName1); - manager.getRedirectResult().then(function(result) { - assertObjectEquals(expectedRedirectResult, result); - asyncTestCase.signal(); - }); - // Clear redirect result on a pending operation should have no effect. - // Above getRedirectResult() should still resolve with expected first result. - manager.clearRedirectResult(); - manager.getRedirectAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler).then(function() { - // Popup resolve should not be called as this is not a popup event. - assertEquals(0, handler.resolvePendingPopupEvent.getCallCount()); - // Clear redirect result after resolved operation should reset result. - manager.clearRedirectResult(); - return manager.getRedirectResult(); - }).then(function(result) { - assertNull(result.user); - // Call Second time. - return manager.getRedirectAuthEventProcessor().processAuthEvent( - expectedAuthEvent, handler); - }).then(function() { - assertEquals(0, handler.resolvePendingPopupEvent.getCallCount()); - // getRedirectResult should resolve with the second expected result. - return manager.getRedirectResult(); - }).then(function(result) { - assertObjectEquals(expectedRedirectResult2, result); - // Clear redirect result after resolved operation should reset result. - manager.clearRedirectResult(); - return manager.getRedirectResult(); - }).then(function(result) { - assertNull(result.user); - asyncTestCase.signal(); - }); -} diff --git a/packages/auth/test/authsettings_test.js b/packages/auth/test/authsettings_test.js deleted file mode 100644 index b1f1dd8d63d..00000000000 --- a/packages/auth/test/authsettings_test.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Tests for authsettings.js. - */ - -goog.provide('fireauth.AuthSettingsTest'); - -goog.require('fireauth.AuthSettings'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.AuthSettingsTest'); - - -function testAuthSettings() { - var authSettings = new fireauth.AuthSettings(); - assertFalse(authSettings.getAppVerificationDisabledForTesting()); - assertFalse(authSettings.appVerificationDisabled); - - authSettings.setAppVerificationDisabledForTesting(true); - assertTrue(authSettings.getAppVerificationDisabledForTesting()); - assertTrue(authSettings.appVerificationDisabled); - - authSettings.appVerificationDisabled = false; - assertFalse(authSettings.getAppVerificationDisabledForTesting()); - assertFalse(authSettings.appVerificationDisabled); - - authSettings.appVerificationDisabled = true; - assertTrue(authSettings.getAppVerificationDisabledForTesting()); - assertTrue(authSettings.appVerificationDisabled); -} diff --git a/packages/auth/test/authstorage_test.js b/packages/auth/test/authstorage_test.js deleted file mode 100644 index e26b817e689..00000000000 --- a/packages/auth/test/authstorage_test.js +++ /dev/null @@ -1,1326 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for authstorage.js - */ - -goog.provide('fireauth.authStorageTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authStorage'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -/** @suppress {extraRequire} Needed for firebase.app().auth() */ -goog.require('fireauth.exports'); -goog.require('fireauth.storage.IndexedDB'); -goog.require('fireauth.storage.LocalStorage'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.SessionStorage'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.array'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.events'); -goog.require('goog.testing.events.Event'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.authStorageTest'); - - -var config = { - apiKey: 'apiKey1' -}; -var stubs = new goog.testing.PropertyReplacer(); -var appId = 'appId1'; -var clock; -var mockLocalStorage; -var mockSessionStorage; - - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); - clock = new goog.testing.MockClock(true); - window.localStorage.clear(); - window.sessionStorage.clear(); -} - - -function tearDown() { - stubs.reset(); - goog.dispose(clock); -} - - -/** - * @return {!fireauth.authStorage.Manager} The default local storage - * synchronized manager instance used for testing. - */ -function getDefaultManagerInstance() { - return new fireauth.authStorage.Manager('firebase', ':', false, true, true); -} - - -function testValidatePersistenceArgument_validAndSupported() { - assertNotThrows(function() { - fireauth.authStorage.validatePersistenceArgument('local'); - fireauth.authStorage.validatePersistenceArgument('session'); - fireauth.authStorage.validatePersistenceArgument('none'); - }); -} - - -function testValidatePersistenceArgument_invalid() { - var invalidTypeError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_PERSISTENCE); - var invalidOptions = ['LOCAL', 'bla', null, {}, true, ['none']]; - for (var i = 0; i < invalidOptions.length; i++) { - fireauth.common.testHelper.assertErrorEquals( - invalidTypeError, - assertThrows(function() { - fireauth.authStorage.validatePersistenceArgument(invalidOptions[i]); - })); - } -} - - -function testValidatePersistenceArgument_node() { - // Simulate Node.js. - stubs.replace( - fireauth.util, - 'getEnvironment', - function() { - return fireauth.util.Env.NODE; - }); - var unsupportedTypeError = new fireauth.AuthError( - fireauth.authenum.Error.UNSUPPORTED_PERSISTENCE); - // Local should throw an error. - fireauth.common.testHelper.assertErrorEquals( - unsupportedTypeError, - assertThrows(function() { - fireauth.authStorage.validatePersistenceArgument('local'); - })); - // Session should throw an error. - fireauth.common.testHelper.assertErrorEquals( - unsupportedTypeError, - assertThrows(function() { - fireauth.authStorage.validatePersistenceArgument('session'); - })); - // None should be supported. - assertNotThrows(function() { - fireauth.authStorage.validatePersistenceArgument('none'); - }); -} - -function testValidatePersistenceArgument_worker() { - // Simulate worker environment. - stubs.replace( - fireauth.util, - 'getEnvironment', - function() { - return fireauth.util.Env.WORKER; - }); - // Simulate indexedDB supported. - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return true; - }); - var unsupportedTypeError = new fireauth.AuthError( - fireauth.authenum.Error.UNSUPPORTED_PERSISTENCE); - // Session should throw an error. - fireauth.common.testHelper.assertErrorEquals( - unsupportedTypeError, - assertThrows(function() { - fireauth.authStorage.validatePersistenceArgument('session'); - })); - // Local should not throw an error when indexedDB is supported. - assertNotThrows(function() { - fireauth.authStorage.validatePersistenceArgument('local'); - }); - // None should be supported. - assertNotThrows(function() { - fireauth.authStorage.validatePersistenceArgument('none'); - }); - - // Simulate indexedDB not supported. - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return false; - }); - // Local should throw an error when indexedDB not supported. - fireauth.common.testHelper.assertErrorEquals( - unsupportedTypeError, - assertThrows(function() { - fireauth.authStorage.validatePersistenceArgument('local'); - })); -} - - -function testValidatePersistenceArgument_reactNative() { - // Simulate React-Native. - stubs.replace( - fireauth.util, - 'getEnvironment', - function() { - return fireauth.util.Env.REACT_NATIVE; - }); - var unsupportedTypeError = new fireauth.AuthError( - fireauth.authenum.Error.UNSUPPORTED_PERSISTENCE); - // Only session should throw an error. - fireauth.common.testHelper.assertErrorEquals( - unsupportedTypeError, - assertThrows(function() { - fireauth.authStorage.validatePersistenceArgument('session'); - })); - // Local and none should be supported. - assertNotThrows(function() { - fireauth.authStorage.validatePersistenceArgument('local'); - fireauth.authStorage.validatePersistenceArgument('none'); - }); -} - - -function testValidatePersistenceArgument_browser() { - // Browser or other. - stubs.replace( - fireauth.util, - 'getEnvironment', - function() { - return fireauth.util.Env.BROWSER; - }); - // Simulate web storage supported. - stubs.replace( - fireauth.util, - 'isWebStorageSupported', - function() { - return true; - }); - var unsupportedTypeError = new fireauth.AuthError( - fireauth.authenum.Error.UNSUPPORTED_PERSISTENCE); - // Should be supported when web storage is. - assertNotThrows(function() { - fireauth.authStorage.validatePersistenceArgument('local'); - fireauth.authStorage.validatePersistenceArgument('session'); - fireauth.authStorage.validatePersistenceArgument('none'); - }); - // Simulate web storage not supported. - stubs.replace( - fireauth.util, - 'isWebStorageSupported', - function() { - return false; - }); - // Only none should work. - assertNotThrows(function() { - fireauth.authStorage.validatePersistenceArgument('none'); - }); - // Local should throw an error. - fireauth.common.testHelper.assertErrorEquals( - unsupportedTypeError, - assertThrows(function() { - fireauth.authStorage.validatePersistenceArgument('local'); - })); - // Session should throw an error. - fireauth.common.testHelper.assertErrorEquals( - unsupportedTypeError, - assertThrows(function() { - fireauth.authStorage.validatePersistenceArgument('session'); - })); -} - - -function testWebStorageNotSupported() { - // Test when web storage not supported. In memory storage should be used - // instead. - stubs.reset(); - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return false; - }); - stubs.replace( - fireauth.storage.LocalStorage, - 'isAvailable', - function() { - return false; - }); - stubs.replace( - fireauth.storage.SessionStorage, - 'isAvailable', - function() { - return false; - }); - // The following should not fail and should create in memory storage - // instances. - var manager = getDefaultManagerInstance(); - var tempKey = {name: 'temporary', persistent: 'session'}; - var tempStorageKey = 'firebase:temporary:appId1'; - var persistentKey = {name: 'persistent', persistent: 'local'}; - var persistentStorageKey = 'firebase:persistent:appId1'; - var expectedValue = 'something'; - // Get, set and remove should work as expected on both types of storage. - return goog.Promise.resolve() - .then(function() { - // Test temporary storage. - return manager.set(tempKey, expectedValue, appId); - }) - .then(function() { - return manager.get(tempKey, appId); - }) - .then(function(value) { - // Nothing should be stored in sessionStorage. - assertNull(window.sessionStorage.getItem(tempStorageKey)); - assertObjectEquals(expectedValue, value); - }) - .then(function() { - return manager.remove(tempKey, appId); - }) - .then(function() { - return manager.get(tempKey, appId); - }) - .then(function(value) { - assertUndefined(value); - // Test persistent storage. - return manager.set(persistentKey, expectedValue, appId); - }) - .then(function() { - return manager.get(persistentKey, appId); - }) - .then(function(value) { - // Nothing should be stored in localStorage. - assertNull(window.localStorage.getItem(persistentStorageKey)); - assertObjectEquals(expectedValue, value); - }) - .then(function() { - return manager.remove(persistentKey, appId); - }) - .then(function() { - return manager.get(persistentKey, appId); - }) - .then(function(value) { - assertUndefined(value); - }); -} - - -function testGetSet_temporaryStorage() { - var manager = getDefaultManagerInstance(); - var key = {name: 'temporary', persistent: 'session'}; - var expectedValue = 'something'; - var storageKey = 'firebase:temporary:appId1'; - return goog.Promise.resolve() - .then(function() { - return manager.set(key, expectedValue, appId); - }) - .then(function() { - return manager.get(key, appId); - }) - .then(function(value) { - assertObjectEquals(expectedValue, value); - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertObjectEquals(expectedValue, value); - }) - .then(function() { - return manager.remove(key, appId); - }) - .then(function() { - return manager.get(key, appId); - }) - .then(function(value) { - assertUndefined(value); - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - }); -} - - -function testGetSet_inMemoryStorage() { - var manager = getDefaultManagerInstance(); - var key = {name: 'temporary', persistent: 'none'}; - var expectedValue = 'something'; - var storageKey = 'firebase:none:appId1'; - return goog.Promise.resolve() - .then(function() { - return manager.set(key, expectedValue, appId); - }) - .then(function() { - return manager.get(key, appId); - }) - .then(function(value) { - assertObjectEquals(expectedValue, value); - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return manager.remove(key, appId); - }) - .then(function() { - return manager.get(key, appId); - }) - .then(function(value) { - assertUndefined(value); - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - }); -} - - -function testGetSet_persistentStorage() { - var manager = getDefaultManagerInstance(); - var key = {name: 'persistent', persistent: 'local'}; - var expectedValue = 'something'; - var storageKey = 'firebase:persistent:appId1'; - return goog.Promise.resolve() - .then(function() { - return manager.set(key, expectedValue, appId); - }) - .then(function() { - return manager.get(key, appId); - }) - .then(function(value) { - assertObjectEquals(expectedValue, value); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertObjectEquals(expectedValue, value); - return manager.remove(key, appId); - }) - .then(function() { - return manager.get(key, appId); - }) - .then(function(value) { - assertUndefined(value); - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - }); -} - - -function testMigrateFromLocalStorage_previouslyPersistedWithLocalStorage() { - var manager = getDefaultManagerInstance(); - var key = {name: 'persistent', persistent: 'local'}; - var expectedValue = 'something'; - var storageKey = 'firebase:persistent:appId1'; - // Save expected value to window.localStorage initially. - window.localStorage.setItem(storageKey, JSON.stringify(expectedValue)); - return manager.migrateFromLocalStorage(key, appId) - .then(function() { - return manager.get(key, appId); - }) - .then(function(value) { - // Data should be migrated from window.localStorage to mockLocalStorage. - assertEquals(expectedValue, value); - assertNull(window.localStorage.getItem(storageKey)); - }); -} - - -function testMigrateFromLocalStorage_multiplePersistentStorage() { - var manager = getDefaultManagerInstance(); - var key = {name: 'persistent', persistent: 'local'}; - var expectedValue = 'something'; - var expectedValue2 = 'somethingElse'; - var storageKey = 'firebase:persistent:appId1'; - // Save expected value to mockLocalStorage. - mockLocalStorage.set(storageKey, expectedValue); - // Save second expected value to window.localStorage. - window.localStorage.setItem(storageKey, JSON.stringify(expectedValue2)); - return manager.migrateFromLocalStorage(key, appId) - .then(function() { - return manager.get(key, appId); - }) - .then(function(value) { - // mockLocalStorage will take precedence over window.localStorage. - assertEquals(expectedValue, value); - assertNull(window.localStorage.getItem(storageKey)); - }); -} - - -function testGetSet_persistentStorage_noId() { - var manager = getDefaultManagerInstance(); - var key = {name: 'persistent', persistent: 'local'}; - var expectedValue = 'something'; - var storageKey = 'firebase:persistent'; - return goog.Promise.resolve() - .then(function() { - return manager.set(key, expectedValue); - }) - .then(function() { - return manager.get(key); - }) - .then(function(value) { - assertObjectEquals(expectedValue, value); - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertObjectEquals(expectedValue, value); - return manager.remove(key); - }) - .then(function() { - return manager.get(key); - }) - .then(function(value) { - assertUndefined(value); - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - }); -} - - -function testAddRemoveListeners_persistentStorage() { - var manager = - new fireauth.authStorage.Manager('name', ':', false, true, true); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - var key1 = {'name': 'authUser', 'persistent': true}; - var key2 = {'name': 'authEvent', 'persistent': true}; - return goog.Promise.resolve() - .then(function() { - return mockLocalStorage.set('name:authUser:appId1', {'foo': 'bar'}); - }) - .then(function() { - return mockLocalStorage.set('name:authEvent:appId1', {'foo': 'bar'}); - }) - .then(function() { - // Add listeners for 2 events. - manager.addListener(key1, 'appId1', listener1); - manager.addListener(key2, 'appId1', listener2); - manager.addListener(key1, 'appId1', listener3); - var storageEvent = new goog.testing.events.Event( - goog.events.EventType.STORAGE, window); - // Trigger user event. - storageEvent.key = 'name:authUser:appId1'; - storageEvent.newValue = null; - mockLocalStorage.fireBrowserEvent(storageEvent); - // Listener 1 and 3 should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - storageEvent = new goog.testing.events.Event( - goog.events.EventType.STORAGE, window); - // Trigger second event. - storageEvent.key = 'name:authEvent:appId1'; - storageEvent.newValue = null; - mockLocalStorage.fireBrowserEvent(storageEvent); - // Only second listener should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - storageEvent = new goog.testing.events.Event( - goog.events.EventType.STORAGE, window); - // Some unknown event. - storageEvent.key = 'key3'; - storageEvent.newValue = null; - mockLocalStorage.fireBrowserEvent(storageEvent); - // No listeners should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - // Remove all listeners. - manager.removeListener(key1, 'appId1', listener1); - manager.removeListener(key2, 'appId1', listener2); - manager.removeListener(key1, 'appId1', listener3); - // Trigger first event. - storageEvent = new goog.testing.events.Event( - goog.events.EventType.STORAGE, window); - storageEvent.key = 'name:authUser:appId1'; - storageEvent.newValue = JSON.stringify({'foo': 'bar'}); - mockLocalStorage.fireBrowserEvent(storageEvent); - // No change. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - }); -} - - -function testAddRemoveListeners_localStorage() { - stubs.reset(); - // localStorage is used when service workers are not supported. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() { - return false; - }); - var manager = - new fireauth.authStorage.Manager('name', ':', false, true, true); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - var key1 = {'name': 'authUser', 'persistent': true}; - var key2 = {'name': 'authEvent', 'persistent': true}; - window.localStorage.setItem( - 'name:authUser:appId1', JSON.stringify({'foo': 'bar'})); - window.localStorage.setItem( - 'name:authEvent:appId1', JSON.stringify({'foo': 'bar'})); - // Add listeners for 2 events. - manager.addListener(key1, 'appId1', listener1); - manager.addListener(key2, 'appId1', listener2); - manager.addListener(key1, 'appId1', listener3); - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - // Trigger user event. - storageEvent.key = 'name:authUser:appId1'; - storageEvent.newValue = null; - window.localStorage.removeItem('name:authUser:appId1'); - goog.testing.events.fireBrowserEvent(storageEvent); - // Listener 1 and 3 should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - // Trigger second event. - storageEvent.key = 'name:authEvent:appId1'; - storageEvent.newValue = null; - window.localStorage.removeItem('name:authEvent:appId1'); - goog.testing.events.fireBrowserEvent(storageEvent); - // Only second listener should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - // Some unknown event. - storageEvent.key = 'key3'; - storageEvent.newValue = null; - goog.testing.events.fireBrowserEvent(storageEvent); - // No listeners should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - // Remove all listeners. - manager.removeListener(key1, 'appId1', listener1); - manager.removeListener(key2, 'appId1', listener2); - manager.removeListener(key1, 'appId1', listener3); - // Trigger first event. - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'name:authUser:appId1'; - window.localStorage.setItem( - 'name:authUser:appId1', JSON.stringify({'foo': 'bar'})); - storageEvent.newValue = JSON.stringify({'foo': 'bar'}); - goog.testing.events.fireBrowserEvent(storageEvent); - // No change. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); -} - - -function testAddRemoveListeners_localStorage_nullKey() { - stubs.reset(); - // Simulate localStorage is used for persistent storage. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() {return false;}); - var manager = - new fireauth.authStorage.Manager('name', ':', false, true, true); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - var listener4 = goog.testing.recordFunction(); - var key1 = {'name': 'authUser', 'persistent': true}; - var key2 = {'name': 'authEvent', 'persistent': true}; - var key3 = {'name': 'other', 'persistent': true}; - // Save existing data for key1 and key2. - var storageKey1 = 'name:authUser:appId1'; - window.localStorage.setItem(storageKey1, JSON.stringify({'foo': 'bar'})); - var storageKey2 = 'name:authEvent:appId1'; - window.localStorage.setItem(storageKey2, JSON.stringify({'foo2': 'bar2'})); - // Add listeners for 3 keys. - manager.addListener(key1, 'appId1', listener1); - manager.addListener(key2, 'appId1', listener2); - manager.addListener(key1, 'appId1', listener3); - manager.addListener(key3, 'appId1', listener4); - // No listener should trigger initially. - assertEquals(0, listener1.getCallCount()); - assertEquals(0, listener2.getCallCount()); - assertEquals(0, listener3.getCallCount()); - assertEquals(0, listener4.getCallCount()); - // Simulate a storage even will null key. - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - // Trigger event with null key (localStorage completely cleared via developer - // tools). - storageEvent.key = null; - storageEvent.newValue = null; - window.localStorage.removeItem(storageKey1); - window.localStorage.removeItem(storageKey2); - goog.testing.events.fireBrowserEvent(storageEvent); - // Listener 1, 2 and 3 should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener2.getCallCount()); - assertEquals(1, listener3.getCallCount()); - // No change in key3 value. - assertEquals(0, listener4.getCallCount()); -} - - -function testAddRemoveListeners_localStorage_ie10() { - stubs.reset(); - // Simulate localStorage is used for persistent storage. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() { - return false; - }); - // Simulate IE 10 with localStorage events not synchronized. - // event.newValue will not be immediately equal to - // localStorage.getItem(event.key). - stubs.replace( - fireauth.util, - 'isIe10', - function() { - return true; - }); - var manager = - new fireauth.authStorage.Manager('name', ':', false, true, true); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - var key1 = {'name': 'authUser', 'persistent': true}; - var key2 = {'name': 'authEvent', 'persistent': true}; - window.localStorage.setItem( - 'name:authUser:appId1', JSON.stringify({'foo': 'bar'})); - window.localStorage.setItem( - 'name:authEvent:appId1', JSON.stringify({'foo': 'bar'})); - window.localStorage.setItem('key3', JSON.stringify({'foo': 'bar'})); - // Add listeners for 2 events. - manager.addListener(key1, 'appId1', listener1); - manager.addListener(key2, 'appId1', listener2); - manager.addListener(key1, 'appId1', listener3); - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - // Trigger user event. - storageEvent.key = 'name:authUser:appId1'; - storageEvent.oldValue = JSON.stringify({'foo': 'bar'}); - storageEvent.newValue = null; - goog.testing.events.fireBrowserEvent(storageEvent); - window.localStorage.removeItem('name:authUser:appId1'); - // Simulate some delay for localStorage event newValue to be available in - // localStorage.getItem(event.key). - clock.tick(fireauth.authStorage.IE10_LOCAL_STORAGE_SYNC_DELAY); - // Listener 1 and 3 should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - // Trigger second event. - storageEvent.key = 'name:authEvent:appId1'; - storageEvent.oldValue = JSON.stringify({'foo': 'bar'}); - storageEvent.newValue = null; - goog.testing.events.fireBrowserEvent(storageEvent); - window.localStorage.removeItem('name:authEvent:appId1'); - // Simulate some delay for localStorage event newValue to be available in - // localStorage.getItem(event.key). - clock.tick(fireauth.authStorage.IE10_LOCAL_STORAGE_SYNC_DELAY); - // Only second listener should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - // Some unknown event. - storageEvent.key = 'key3'; - storageEvent.oldValue = JSON.stringify({'foo': 'bar'}); - storageEvent.newValue = null; - goog.testing.events.fireBrowserEvent(storageEvent); - window.localStorage.removeItem('key3'); - // Simulate some delay for localStorage event newValue to be available in - // localStorage.getItem(event.key). - clock.tick(fireauth.authStorage.IE10_LOCAL_STORAGE_SYNC_DELAY); - // No listeners should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - // Remove all listeners. - manager.removeListener(key1, 'appId1', listener1); - manager.removeListener(key2, 'appId1', listener2); - manager.removeListener(key1, 'appId1', listener3); - // Trigger first event. - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'name:authUser:appId1'; - storageEvent.newValue = JSON.stringify({'foo': 'bar'}); - storageEvent.oldValue = null; - goog.testing.events.fireBrowserEvent(storageEvent); - window.localStorage.setItem( - 'name:authUser:appId1', JSON.stringify({'foo': 'bar'})); - // Simulate some delay for localStorage event newValue to be available in - // localStorage.getItem(event.key). - clock.tick(fireauth.authStorage.IE10_LOCAL_STORAGE_SYNC_DELAY); - // No change. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); -} - - -function testAddRemoveListeners_indexeddb() { - stubs.reset(); - // Mock indexedDB local storage manager. - var mockIndexeddb = { - handlers: [], - addStorageListener: function(indexeddbHandler) { - mockIndexeddb.handlers.push(indexeddbHandler); - }, - removeStorageListener: function(indexeddbHandler) { - goog.array.remove(mockIndexeddb.handlers, indexeddbHandler); - }, - trigger: function(key) { - // Trigger all listeners on key. - for (var i = 0; i < mockIndexeddb.handlers.length; i++) { - // Trigger key change. - mockIndexeddb.handlers[i]([key]); - } - } - }; - // Simulate indexedDB is used for persistent storage. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() { - return true; - }); - // Use mock indexedDB for hybrid indexedDB. - stubs.replace( - fireauth.storage, - 'HybridIndexedDB', - function() { - return mockIndexeddb; - }); - var manager = - new fireauth.authStorage.Manager('name', ':', false, true, true); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - var key1 = {'name': 'authUser', 'persistent': true}; - var key2 = {'name': 'authEvent', 'persistent': true}; - // Add listeners for 2 events. - manager.addListener(key1, 'appId1', listener1); - manager.addListener(key2, 'appId1', listener2); - manager.addListener(key1, 'appId1', listener3); - // Trigger user event. - mockIndexeddb.trigger('name:authUser:appId1'); - // Listener 1 and 3 should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - // Trigger second event. - mockIndexeddb.trigger('name:authEvent:appId1'); - // Only second listener should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - // Some unknown event. - mockIndexeddb.trigger('key3'); - // No listeners should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - // Remove all listeners. - manager.removeListener(key1, 'appId1', listener1); - manager.removeListener(key2, 'appId1', listener2); - manager.removeListener(key1, 'appId1', listener3); - // Trigger first event. - mockIndexeddb.trigger('name:authUser:appId1'); - // No change. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); -} - - -function testAddRemoveListeners_indexeddb_cannotRunInBackground() { - // Even when app cannot run in the background and localStorage is not synced - // between iframe and popup, polling web storage function should not be used. - // indexedDB should be used. - // Mock indexedDB local storage manager. - stubs.reset(); - var mockIndexeddb = { - handlers: [], - addStorageListener: function(indexeddbHandler) { - mockIndexeddb.handlers.push(indexeddbHandler); - }, - removeStorageListener: function(indexeddbHandler) { - goog.array.remove(mockIndexeddb.handlers, indexeddbHandler); - }, - trigger: function(key) { - // Trigger all listeners on key. - for (var i = 0; i < mockIndexeddb.handlers.length; i++) { - // Trigger key change. - mockIndexeddb.handlers[i]([key]); - } - } - }; - // Simulate indexedDB is used for persistent storage. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() { - return true; - }); - // Use mock indexedDB for hybrid indexedDB. - stubs.replace( - fireauth.storage, - 'HybridIndexedDB', - function() { - return mockIndexeddb; - }); - // Cannot run in the background. - var manager = - new fireauth.authStorage.Manager('name', ':', false, false, true); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - var key1 = {'name': 'authUser', 'persistent': true}; - var key2 = {'name': 'authEvent', 'persistent': true}; - // Add listeners for 2 events. - manager.addListener(key1, 'appId1', listener1); - manager.addListener(key2, 'appId1', listener2); - manager.addListener(key1, 'appId1', listener3); - // Trigger user event. - mockIndexeddb.trigger('name:authUser:appId1'); - // Listener 1 and 3 should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - // Trigger second event. - mockIndexeddb.trigger('name:authEvent:appId1'); - // Only second listener should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - // Some unknown event. - mockIndexeddb.trigger('key3'); - // No listeners should trigger. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); - // Remove all listeners. - manager.removeListener(key1, 'appId1', listener1); - manager.removeListener(key2, 'appId1', listener2); - manager.removeListener(key1, 'appId1', listener3); - // Trigger first event. - mockIndexeddb.trigger('name:authUser:appId1'); - // No change. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(1, listener2.getCallCount()); -} - - -function testSafariLocalStorageSync_newEvent() { - stubs.reset(); - // Simulate persistent state implemented through localStorage for Safari. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() {return false;}); - var manager = - new fireauth.authStorage.Manager('firebase', ':', true, true, true); - // Simulate Safari bug. - stubs.replace( - fireauth.util, - 'isSafariLocalStorageNotSynced', - function() {return true;}); - var key1 = {'name': 'authEvent', 'persistent': true}; - var listener1 = goog.testing.recordFunction(); - var expectedEvent = { - type: 'signInViaPopup', - eventId: '1234', - callbackUrl: 'http://www.example.com/#oauthResponse', - sessionId: 'SESSION_ID' - }; - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'firebase:authEvent:appId1'; - // New Auth event. - storageEvent.oldValue = null; - storageEvent.newValue = JSON.stringify(expectedEvent); - manager.addListener(key1, 'appId1', listener1); - // This should force localStorage sync. - goog.testing.events.fireBrowserEvent(storageEvent); - // Auth event should be synchronized. - assertEquals( - storageEvent.newValue, - window.localStorage.getItem(storageEvent.key)); - assertEquals(1, listener1.getCallCount()); - manager.removeListener(key1, 'appId1', listener1); - goog.testing.events.fireBrowserEvent(storageEvent); - // No further call. - assertEquals(1, listener1.getCallCount()); -} - - -function testSafariLocalStorageSync_cannotRunInBackground() { - stubs.reset(); - // Simulate persistent state implemented through localStorage for Safari. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() {return false;}); - // This simulates iframe embedded in a cross origin domain. - // Realistically only storage event should trigger here. - // Test when new data is added to storage. - var manager = - new fireauth.authStorage.Manager('firebase', ':', true, false, true); - // Simulate Safari bug. - stubs.replace( - fireauth.util, - 'isSafariLocalStorageNotSynced', - function() {return true;}); - var key1 = {'name': 'authEvent', 'persistent': true}; - var listener1 = goog.testing.recordFunction(); - var expectedEvent = { - type: 'signInViaPopup', - eventId: '1234', - callbackUrl: 'http://www.example.com/#oauthResponse', - sessionId: 'SESSION_ID' - }; - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'firebase:authEvent:appId1'; - // New Auth event. - storageEvent.oldValue = null; - storageEvent.newValue = JSON.stringify(expectedEvent); - manager.addListener(key1, 'appId1', listener1); - // This should force localStorage sync. - goog.testing.events.fireBrowserEvent(storageEvent); - // Auth event should be synchronized. - assertEquals( - storageEvent.newValue, - window.localStorage.getItem(storageEvent.key)); - assertEquals(1, listener1.getCallCount()); - manager.removeListener(key1, 'appId1', listener1); - goog.testing.events.fireBrowserEvent(storageEvent); - // No further call. - assertEquals(1, listener1.getCallCount()); -} - - -function testSafariLocalStorageSync_deletedEvent() { - stubs.reset(); - // Simulate persistent state implemented through localStorage for Safari. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() {return false;}); - // This simulates iframe embedded in a cross origin domain. - // Realistically only storage event should trigger here. - // Test when old data is deleted from storage. - var manager = - new fireauth.authStorage.Manager('firebase', ':', true, true, true); - var key1 = {'name': 'authEvent', 'persistent': true}; - // Simulate Safari bug. - stubs.replace( - fireauth.util, - 'isSafariLocalStorageNotSynced', - function() {return true;}); - var listener1 = goog.testing.recordFunction(); - var expectedEvent = { - type: 'signInViaPopup', - eventId: '1234', - callbackUrl: 'http://www.example.com/#oauthResponse', - sessionId: 'SESSION_ID' - }; - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'firebase:authEvent:appId1'; - // Deleted Auth event. - storageEvent.oldValue = JSON.stringify(expectedEvent); - storageEvent.newValue = null; - window.localStorage.setItem(storageEvent.key, storageEvent.oldValue); - manager.addListener(key1, 'appId1', listener1); - // This should force localStorage sync. - goog.testing.events.fireBrowserEvent(storageEvent); - // Auth event should be synchronized. - assertNull(window.localStorage.getItem(storageEvent.key)); - assertEquals(1, listener1.getCallCount()); - manager.removeListener(key1, 'appId1', listener1); - goog.testing.events.fireBrowserEvent(storageEvent); - // No further call. - assertEquals(1, listener1.getCallCount()); -} - - -function testRunsInBackground_storageEventMode() { - // Test when browser does not run in the background while another tab is in - // foreground. - // Test when storage event is first detected. Polling should be disabled to - // prevent duplicate storage detection. - stubs.reset(); - // Simulate localStorage is used for persistent storage. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() {return false;}); - var key = {name: 'authEvent', persistent: 'local'}; - var storageKey = 'firebase:authEvent:appId1'; - var manager = new fireauth.authStorage.Manager( - 'firebase', ':', false, false, true); - var listener1 = goog.testing.recordFunction(); - var expectedEvent = { - type: 'signInViaPopup', - eventId: '1234', - callbackUrl: 'http://www.example.com/#oauthResponse', - sessionId: 'SESSION_ID' - }; - // Simulate storage event. - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'firebase:authEvent:appId1'; - // New Auth event. - storageEvent.oldValue = null; - storageEvent.newValue = JSON.stringify(expectedEvent); - - // Add listener. - manager.addListener(key, appId, listener1); - // Test that storage event listener is not set. - // This should not be detected. - window.localStorage.setItem( - storageKey, JSON.stringify(expectedEvent)); - goog.testing.events.fireBrowserEvent(storageEvent); - // Listener should trigger. - assertEquals(1, listener1.getCallCount()); - // Clear storage. - window.localStorage.clear(); - // Save Auth event and confirm listener not triggered. - // This simulates polling. - window.localStorage.setItem( - storageKey, JSON.stringify(expectedEvent)); - // Run clock. - clock.tick(1000); - // Duplicate polling event should not trigger. - assertEquals(1, listener1.getCallCount()); - // Remove listener. - manager.removeListener(key, appId, listener1); - // Simulate another storage to same item. - goog.testing.events.fireBrowserEvent(storageEvent); - // Listener should not be triggered as it has been removed. - assertEquals(1, listener1.getCallCount()); -} - - -function testRunsInBackground_webStorageNotSupported() { - // Test when browser does not run in the background and web storage is not - // supported. Polling should not be turned on. - var key = {name: 'authEvent', persistent: 'local'}; - var storageKey = 'firebase:authEvent:appId1'; - // Simulate manager doesn't support web storage and can't run in the - // background. Normally when a browser can't run in the background, polling is - // enabled. - var manager = new fireauth.authStorage.Manager( - 'firebase', ':', false, false, false); - var listener1 = goog.testing.recordFunction(); - var expectedEvent = { - type: 'signInViaPopup', - eventId: '1234', - callbackUrl: 'http://www.example.com/#oauthResponse', - sessionId: 'SESSION_ID' - }; - - // Add listener. - manager.addListener(key, appId, listener1); - // Test that polling function is not set by updating localStorage with some - // data. This should not happen realistically when web storage is disabled. - window.localStorage.setItem(storageKey, JSON.stringify(expectedEvent)); - // Run clock. - clock.tick(1000); - // Listener should not trigger. - assertEquals(0, listener1.getCallCount()); - // Clear storage. - window.localStorage.clear(); - // Run clock. - clock.tick(1000); - // Listener should not trigger. - assertEquals(0, listener1.getCallCount()); - // Save Auth event and confirm listener not triggered. - // This normally simulates polling. - window.localStorage.setItem(storageKey, JSON.stringify(expectedEvent)); - // Run clock. - clock.tick(1000); - // Listener should not trigger. - assertEquals(0, listener1.getCallCount()); -} - - -function testRunsInBackground_pollingMode() { - stubs.reset(); - // Simulate localStorage is used for persistent storage. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() {return false;}); - // Test when browser does not run in the background while another tab is in - // foreground. - // Test when storage polling first detects a storage notification. - // Storage event listener should be disabled to prevent duplicate storage - // detection. - var key = {name: 'authEvent', persistent: 'local'}; - var storageKey = 'firebase:authEvent:appId1'; - var manager = new fireauth.authStorage.Manager( - 'firebase', ':', false, false, true); - var listener1 = goog.testing.recordFunction(); - var expectedEvent = { - type: 'signInViaPopup', - eventId: '1234', - callbackUrl: 'http://www.example.com/#oauthResponse', - sessionId: 'SESSION_ID' - }; - // Simulate storage event. Don't trigger yet. - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'firebase:authEvent:appId1'; - // New auth event. - storageEvent.oldValue = null; - storageEvent.newValue = JSON.stringify(expectedEvent); - - // Add listener. - manager.addListener(key, appId, listener1); - // Simulate storage update in some other tab and storage event not - // triggered. - window.localStorage.setItem( - storageKey, JSON.stringify(expectedEvent)); - // Run clock. - clock.tick(1000); - // Listener should be triggered. - assertEquals(1, listener1.getCallCount()); - // Test that duplicate storage event is ignored. - // This should not be detected. - goog.testing.events.fireBrowserEvent(storageEvent); - window.localStorage.setItem( - storageKey, JSON.stringify(expectedEvent)); - // Listener should not trigger. - assertEquals(1, listener1.getCallCount()); - // Remove listener. - manager.removeListener(key, appId, listener1); - // Simulate another storage to same item. - window.localStorage.removeItem(storageKey); - // Run clock. - clock.tick(1000); - // Listener should not be triggered as it has been removed. - assertEquals(1, listener1.getCallCount()); -} - - -function testRunsInBackground_currentTabChangesIgnored() { - stubs.reset(); - // Simulate localStorage is used for persistent storage. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() {return false;}); - // Test when browser does not run in the background while another tab is in - // foreground. - // This tests that only other tab changes are detected and current tab changes - // are ignored. - var key = {name: 'authEvent', persistent: 'local'}; - var storageKey = 'firebase:authEvent:appId1'; - var manager = new fireauth.authStorage.Manager( - 'firebase', ':', false, false, true); - var listener1 = goog.testing.recordFunction(); - var expectedEvent = { - type: 'signInViaPopup', - eventId: '1234', - callbackUrl: 'http://www.example.com/#oauthResponse', - sessionId: 'SESSION_ID' - }; - // Add listener. - manager.addListener(key, appId, listener1); - // Save Auth event and confirm listener not triggered. - return manager.set(key, expectedEvent, appId).then(function() { - // Changes in same tab should not trigger listener. - assertEquals(0, listener1.getCallCount()); - // Delete Auth event and confirm listener not triggered. - return manager.remove(key, appId); - }).then(function() { - // Changes in same tab should not trigger listener. - assertEquals(0, listener1.getCallCount()); - // Simulate storage update in some other tab and storage event not - // triggered. - window.localStorage.setItem( - storageKey, JSON.stringify(expectedEvent)); - // Run clock. - clock.tick(1000); - // Listener should be triggered. - assertEquals(1, listener1.getCallCount()); - // Remove listener. - manager.removeListener(key, appId, listener1); - // Simulate another storage to same item. - window.localStorage.removeItem(storageKey); - // Run clock. - clock.tick(1000); - // Listener should not be triggered as it has been removed. - assertEquals(1, listener1.getCallCount()); - }); -} diff --git a/packages/auth/test/authuser_test.js b/packages/auth/test/authuser_test.js deleted file mode 100644 index 54168e0b1ec..00000000000 --- a/packages/auth/test/authuser_test.js +++ /dev/null @@ -1,15000 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for authuser.js - */ - -goog.provide('fireauth.AuthUserTest'); - -goog.require('fireauth.ActionCodeSettings'); -goog.require('fireauth.Auth'); -goog.require('fireauth.AuthCredential'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthErrorWithCredential'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.AuthEventManager'); -goog.require('fireauth.AuthUser'); -goog.require('fireauth.AuthUserInfo'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.MultiFactorAssertion'); -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.MultiFactorUser'); -goog.require('fireauth.OAuthSignInHandler'); -goog.require('fireauth.PhoneAuthCredential'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.ProactiveRefresh'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.SAMLAuthProvider'); -goog.require('fireauth.StsTokenManager'); -goog.require('fireauth.TokenRefreshTime'); -goog.require('fireauth.UserEventType'); -goog.require('fireauth.UserMetadata'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('fireauth.deprecation'); -goog.require('fireauth.idp'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.object'); -goog.require('fireauth.storage.PendingRedirectManager'); -goog.require('fireauth.storage.RedirectUserManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Uri'); -goog.require('goog.events'); -goog.require('goog.events.EventTarget'); -goog.require('goog.object'); -goog.require('goog.testing.AsyncTestCase'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.AuthUserTest'); - -var config = { - apiKey: 'apiKey1' -}; -var user = null; -var accountInfo = null; -var accountInfoWithPhone = null; -var providerData1 = null; -var providerData2 = null; -var providerDataPhone = null; -var config1 = null; -var config2 = null; -var rpcHandler = null; -var token = null; -var tokenResponse = null; -var accountInfo2 = null; -var getAccountInfoResponse = null; -var getAccountInfoResponseProviderData1 = null; -var getAccountInfoResponseProviderData2 = null; -// A sample JWT, along with its decoded contents. -var idTokenGmail = { - data: { - iss: 'https://securetoken.google.com/projectId', - auth_time: 1522715325, - sub: '679', - aud: 'projectId', - iat: 1522776807, - provider_id: 'gmail.com', - email: 'test123456@gmail.com', - federated_id: 'https://www.google.com/accounts/123456789', - firebase: { - identities: { - email: [ - 'test123456@gmail.com' - ] - }, - sign_in_provider: 'password' - } - } -}; -var idTokenSaml = { - data: { - iss: 'https://securetoken.google.com/projectId', - sub: '679', - aud: 'projectId', - federated_id: 'https://www.example.com/saml/1234567890', - provider_id: 'saml.provider', - email: 'test123456@gmail.com' - } -}; -var idTokenCustomClaims = { - data: { - iss: 'https://securetoken.google.com/projectId', - name: 'John Doe', - admin: true, - aud: 'projectId', - auth_time: 1522715325, - sub: 'nep2uwNCK4PqjvoKjb0InVJHlGi1', - iat: 1522776807, - email: "testuser@gmail.com", - email_verified: true, - firebase: { - identities: { - email: [ - 'testuser@gmail.com' - ] - }, - sign_in_provider: 'password' - } - } -}; -var expectedTokenResponseWithIdPData; -var expectedAdditionalUserInfo; -var expectedGoogleCredential; -var expectedReauthenticateTokenResponse; -var now = Date.now(); - -var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); - -var stubs = new goog.testing.PropertyReplacer(); -var ignoreArgument; -var mockControl; - -var app; -var auth; -var getAccountInfoResponseGoogleProviderData; -var getAccountInfoResponsePhoneAuthProviderData; -var expectedPhoneNumber; -var appVerifier; -var expectedRecaptchaToken; -var actionCodeSettings = { - 'url': 'https://www.example.com/?state=abc', - 'iOS': { - 'bundleId': 'com.example.ios' - }, - 'android': { - 'packageName': 'com.example.android', - 'installApp': true, - 'minimumVersion': '12' - }, - 'handleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' -}; -var lastLoginAt = '1506050282000'; -var createdAt = '1506044998000'; -var lastLoginAt2 = '1506053999000'; -var createdAt2 = '1505980145000'; -var expectedSamlTokenResponseWithIdPData; -var expectedSamlAdditionalUserInfo; -var jwt; -var newJwt; -var userReloadedEventHandler; -var multiFactor; -var mfaInfo; -var multiFactorErrorServerResponse; -var multiFactorTokenResponse; -var multiFactorGetAccountInfoResponse; -var nonMultiFactorTokenResponse; - - -function setUp() { - // Disable Auth event manager by default unless needed for a specific test. - fireauth.AuthEventManager.ENABLED = false; - config1 = { - apiKey: 'apiKey1', - appName: 'appId1' - }; - config2 = { - apiKey: 'apiKey2', - appName: 'appId2' - }; - idTokenGmail.data.exp = now / 1000 + 3600; - idTokenGmail.jwt = - fireauth.common.testHelper.createMockJwt(idTokenGmail.data); - idTokenSaml.data.exp = now / 1000 + 3600; - idTokenSaml.jwt = fireauth.common.testHelper.createMockJwt(idTokenSaml.data); - idTokenCustomClaims.data.exp = now / 1000 + 3600; - idTokenCustomClaims.jwt = - fireauth.common.testHelper.createMockJwt(idTokenCustomClaims.data); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - // Simulate tab can run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - // In case the tests are run from an iframe. - stubs.replace( - fireauth.util, - 'isIframe', - function() { - return false; - }); - stubs.replace( - Date, - 'now', - function() { - return now; - }); - multiFactor = { - 'enrolledFactors': [ - { - 'uid': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrollmentTime': new Date(now).toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505551234' - }, - { - 'uid': 'ENROLLMENT_UID2', - 'displayName': null, - 'enrollmentTime': new Date(now).toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505556789' - } - ] - }; - mfaInfo = [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+16505551234' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+16505556789' - } - ]; - accountInfo = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt - }; - accountInfoWithPhone = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'phoneNumber': '+16505550101', - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt - }; - accountInfoWithEnrolledFactors = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt, - 'multiFactor': multiFactor - }; - accountInfo2 = { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - // common_typos_disable. - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true, - 'lastLoginAt': lastLoginAt2, - 'createdAt': createdAt2 - }; - - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - 'user1', - 'https://www.example.com/user1/photo.png'); - providerData2 = new fireauth.AuthUserInfo( - 'providerUserId2', - 'providerId2', - 'user2@example.com', - 'user2', - 'https://www.example.com/user2/photo.png'); - providerDataPhone = new fireauth.AuthUserInfo( - '+16505550101', 'phone', undefined, undefined, undefined, '+16505550101'); - rpcHandler = new fireauth.RpcHandler('apiKey1'); - token = new fireauth.StsTokenManager(rpcHandler); - token.setRefreshToken('refreshToken'); - jwt = fireauth.common.testHelper.createMockJwt( - {'group': '1'}, now + 3600 * 1000); - newJwt = fireauth.common.testHelper.createMockJwt( - {'group': '2'}, now + 3600 * 1000); - token.setAccessToken(jwt); - tokenResponse = { - 'idToken': jwt, - 'refreshToken': 'refreshToken', - 'expiresIn': '4800' - }; - - // accountInfo in the format of a getAccountInfo response. - getAccountInfoResponse = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': 'user@default.com', - 'emailVerified': true, - 'phoneNumber': '+16505550101', - 'displayName': 'defaultDisplayName', - 'providerUserInfo': [], - 'photoUrl': 'https://www.default.com/default/default.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt - }] - }; - // providerData1 and providerData2 in the format of a getAccountInfo response. - getAccountInfoResponseProviderData1 = { - 'providerId': 'providerId1', - 'displayName': 'user1', - 'email': 'user1@example.com', - 'photoUrl': 'https://www.example.com/user1/photo.png', - 'rawId': 'providerUserId1' - }; - getAccountInfoResponseProviderData2 = { - 'providerId': 'providerId2', - 'displayName': 'user2', - 'email': 'user2@example.com', - 'photoUrl': 'https://www.example.com/user2/photo.png', - 'rawId': 'providerUserId2' - }; - getAccountInfoResponseGoogleProviderData = { - 'providerId': 'google.com', - 'displayName': 'My Google Name', - 'email': 'me@gmail.com', - 'photoUrl': 'https://www.google.com/me.png', - 'rawId': 'myGoogleId' - }; - getAccountInfoResponsePhoneAuthProviderData = { - 'providerId': 'phone', - 'rawId': '+16505550101', - 'phoneNumber': '+16505550101' - }; - expectedTokenResponseWithIdPData = { - 'idToken': newJwt, - 'refreshToken': 'newRefreshToken', - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + - 'me":{"givenName":"John","familyName":"Doe"}}' - }; - expectedReauthenticateTokenResponse = { - 'idToken': idTokenGmail.jwt, - 'refreshToken': 'myRefreshToken', - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + - 'me":{"givenName":"John","familyName":"Doe"}}' - }; - expectedAdditionalUserInfo = { - 'profile': { - 'kind': 'plus#person', - 'displayName': 'John Doe', - 'name': { - 'givenName': 'John', - 'familyName': 'Doe' - } - }, - 'providerId': 'google.com', - 'isNewUser': false - }; - expectedGoogleCredential = fireauth.GoogleAuthProvider.credential( - 'googleIdToken', 'googleAccessToken'); - expectedSamlTokenResponseWithIdPData = { - 'idToken': newJwt, - 'refreshToken': 'newRefreshToken', - 'providerId': 'saml.provider', - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + - 'me":{"givenName":"John","familyName":"Doe"}}' - }; - expectedSamlAdditionalUserInfo = { - 'profile': { - 'kind': 'plus#person', - 'displayName': 'John Doe', - 'name': { - 'givenName': 'John', - 'familyName': 'Doe' - } - }, - 'providerId': 'saml.provider', - 'isNewUser': false - }; - expectedPhoneNumber = '+16505550101'; - expectedRecaptchaToken = 'RECAPTCHA_TOKEN'; - appVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(expectedRecaptchaToken); - } - }; - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); - nonMultiFactorTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'sub': 'defaultUserId', - 'firebase': { - 'sign_in_provider': 'password' - } - }), - 'refreshToken': 'SINGLE_FACTOR_REFRESH_TOKEN' - }; - multiFactorTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'sub': 'defaultUserId', - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }), - 'refreshToken': 'MULTI_FACTOR_REFRESH_TOKEN' - }; - multiFactorErrorServerResponse = { - 'mfaInfo': [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+*******1234' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+*******6789' - } - ], - 'mfaPendingCredential': 'PENDING_CREDENTIAL', - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + - '"name":{"givenName":"John","familyName":"Doe"}}' - }; - multiFactorGetAccountInfoResponse = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': 'user@default.com', - 'emailVerified': true, - 'displayName': 'defaultDisplayName', - 'providerUserInfo': [], - 'photoUrl': 'https://www.default.com/default/default.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'lastLoginAt': '1506050282000', - 'createdAt': '1506050282000', - 'mfaInfo': [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+16505551234' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrolledAt': new Date(now).toISOString(), - 'phoneInfo': '+16505556789' - } - ] - }] - }; -} - - -function tearDown() { - for (var i = 0; i < firebase.apps.length; i++) { - asyncTestCase.waitForSignals(1); - firebase.apps[i].delete().then(function() { - asyncTestCase.signal(); - }); - } - if (auth) { - auth.delete(); - } - // Reset already initialized Auth event managers. - fireauth.AuthEventManager.manager_ = {}; - user = null; - accountInfo = null; - accountInfoWithPhone = null; - accountInfoWithEnrolledFactors = null; - accountInfo2 = null; - getAccountInfoResponse = null; - getAccountInfoResponseProviderData1 = null; - getAccountInfoResponseProviderData2 = null; - providerData1 = null; - providerData2 = null; - providerDataPhone = null; - rpcHandler = null; - token = null; - tokenResponse = null; - config1 = null; - config2 = null; - multiFactor = null; - mfaInfo = null; - multiFactorErrorServerResponse = null; - multiFactorTokenResponse = null; - multiFactorGetAccountInfoResponse = null; - nonMultiFactorTokenResponse = null; - window.localStorage.clear(); - window.sessionStorage.clear(); - stubs.reset(); - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } -} - - -/** @return {!goog.events.EventTarget} The event dispatcher test object. */ -function createEventDispatcher() { - return new goog.events.EventTarget(); -} - - -/** - * Asserts that token events do not trigger. - * @param {!fireauth.AuthUser} user - */ -function assertNoTokenEvents(user) { - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - fail('Token change should not trigger due to token being unchanged!'); - }); -} - - -/** - * Asserts that user invalidated events do not trigger. - * @param {!fireauth.AuthUser} user - */ -function assertNoUserInvalidatedEvents(user) { - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - fail('User invalidate event should not trigger!'); - }); -} - - -/** - * Asserts that state events do not trigger. - * @param {!fireauth.AuthUser} user - */ -function assertNoStateEvents(user) { - user.addStateChangeListener(function(userTemp) { - fail('State change listener should not trigger!'); - }); -} - - -/** - * Asserts that delete events do not trigger. - * @param {!fireauth.AuthUser} user - */ -function assertNoDeleteEvents(user) { - goog.events.listen( - user, fireauth.UserEventType.USER_DELETED, function(event) { - fail('User deleted listener should not trigger!'); - }); -} - - -/** - * Asserts that a method should fail when user is destroyed and no listeners - * are triggered. - * @param {string} methodName The name of the method of AuthUser that should - * fail if the user is destroyed. - * @param {!Array} parameters The arguments to pass to the method. - */ -function assertFailsWhenUserIsDestroyed(methodName, parameters) { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoDeleteEvents(user); - assertNoUserInvalidatedEvents(user); - - user.destroy(); - user[methodName].apply(user, parameters).then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MODULE_DESTROYED), - error); - asyncTestCase.signal(1); - }); -} - - -function testProviderData() { - assertEquals('providerUserId1', providerData1['uid']); - assertEquals('providerId1', providerData1['providerId']); - assertEquals('user1@example.com', providerData1['email']); - assertEquals('user1', providerData1['displayName']); - assertEquals( - 'https://www.example.com/user1/photo.png', providerData1['photoURL']); -} - - -function testUser() { - accountInfo['email'] = null; - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - - assertObjectEquals( - new fireauth.UserMetadata(createdAt, lastLoginAt), user.metadata); - assertEquals(jwt, user.lastAccessToken_); - assertEquals('defaultUserId', user['uid']); - assertEquals('defaultDisplayName', user['displayName']); - assertNull(user['email']); - assertEquals('https://www.default.com/default/default.png', user['photoURL']); - assertEquals('firebase', user['providerId']); - assertEquals(false, user['isAnonymous']); - assertNull(user['tenantId']); - assertArrayEquals(['providerId1', 'providerId2'], user.getProviderIds()); - assertObjectEquals( - { - 'uid': 'providerUserId1', - 'displayName': null, - 'photoURL': 'https://www.example.com/user1/photo.png', - 'email': 'user1@example.com', - 'providerId': 'providerId1', - 'phoneNumber': null - }, - user['providerData'][0]); - assertObjectEquals( - { - 'uid': 'providerUserId2', - 'displayName': 'user2', - 'photoURL': 'https://www.example.com/user2/photo.png', - 'email': 'user2@example.com', - 'providerId': 'providerId2', - 'phoneNumber': null - }, - user['providerData'][1]); - - // Test popup event ID setters and getters. - assertNull(user.getPopupEventId()); - user.setPopupEventId('1234'); - assertEquals('1234', user.getPopupEventId()); - user.setPopupEventId('5678'); - assertEquals('5678', user.getPopupEventId()); - - // Test redirect event ID setters and getters. - assertNull(user.getRedirectEventId()); - user.setRedirectEventId('1234'); - assertEquals('1234', user.getRedirectEventId()); - user.setRedirectEventId('5678'); - assertEquals('5678', user.getRedirectEventId()); - - // Test ApiKey getter. - assertEquals('apiKey1', user.getApiKey()); -} - - -/** - * Asserts that a user initiated with an emulator config will propagate - * the config to the RPC handler. - */ -function tesUser_initWithEmulator() { - // Listen to emulator config calls on RpcHandler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmulatorConfig', - goog.testing.recordFunction()); - - // Initialize a user. - user = new fireauth.AuthUser( - { - appName: config1.appName, - apiKey: config1.apiKey, - emulatorConfig: { - url: 'http://emulator.test.domain:1234' - } - } - ); - - // Should notify the RPC handler. - assertEquals( - 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); - assertObjectEquals( - { - url: 'http://emulator.test.domain:1234', - }, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0) - ); -} - - -function testUser_multiFactor() { - user = new fireauth.AuthUser( - config1, tokenResponse, accountInfoWithEnrolledFactors); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - - assertTrue(user.multiFactor instanceof fireauth.MultiFactorUser); - assertObjectEquals( - new fireauth.MultiFactorUser(user, accountInfoWithEnrolledFactors), - user.multiFactor); -} - - -function testUser_copyUser() { - config1['authDomain'] = 'subdomain.firebaseapp.com'; - config2['authDomain'] = 'subdomain.firebaseapp.com'; - asyncTestCase.waitForSignals(1); - // Sets the tenant ID on user to be copied. - accountInfo['tenantId'] = 'TENANT_ID'; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - goog.testing.recordFunction(function(idToken) { - // Mocks that tenant ID is returned in getAccountInfo response. - getAccountInfoResponse['users'][0]['tenantId'] = 'TENANT_ID'; - return goog.Promise.resolve(getAccountInfoResponse); - })); - var expectedEventId = '1234'; - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config2['apiKey'], config2['appName'])); - var frameworks = ['firebaseui', 'angularfire']; - - fireauth.AuthEventManager.ENABLED = true; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(false); - mockControl.$replayAll(); - - var copiedUser = fireauth.AuthUser.copyUser( - user, config2, storageManager, frameworks); - // Verifies that user is not reloaded on copying. - assertEquals( - 0, fireauth.RpcHandler.prototype.getAccountInfoByIdToken.getCallCount()); - fireauth.common.testHelper.assertUserEqualsInWithDiffApikey( - user, copiedUser, config1['apiKey'], config2['apiKey']); - assertFalse(copiedUser['isAnonymous']); - assertEquals('TENANT_ID', copiedUser['tenantId']); - // Confirm frameworks set on created user. - assertArrayEquals(frameworks, copiedUser.getFramework()); - - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - copiedUser.enablePopupRedirect(); - copiedUser.linkWithRedirect(expectedProvider).then(function() { - assertEquals(1, - fireauth.RpcHandler.prototype.getAccountInfoByIdToken.getCallCount()); - // Redirect event ID should be saved. - assertEquals(expectedEventId, copiedUser.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals(copiedUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_copyUser_defaultConfig() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - - var copiedUser = fireauth.AuthUser.copyUser(user); - assertObjectEquals(copiedUser.toPlainObject(), user.toPlainObject()); - assertNull(user.getPopupEventId()); - assertNull(user.getRedirectEventId()); -} - - -function testUser_copyUser_expiredToken() { - // Mock the expired token. - tokenResponse['idToken'] = - fireauth.common.testHelper.createMockJwt(null, now - 5); - stubs.replace( - fireauth.RpcHandler.prototype, - 'requestStsToken', - function(data) { - // Copying user should not trigger token refresh even if the token - // expires. - fail('The token should not be refreshed!'); - }); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - var copiedUser = fireauth.AuthUser.copyUser(user); - assertObjectEquals(copiedUser.toPlainObject(), user.toPlainObject()); -} - - -function testUser_copyUser_multiFactor() { - user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user2 = new fireauth.AuthUser( - config1, tokenResponse, accountInfoWithEnrolledFactors); - user3 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - var multiFactorUser1 = user1.multiFactor; - - user1.copy(user2); - // Same reference should be kept. - assertEquals(user1.multiFactor, multiFactorUser1); - assertObjectEquals( - new fireauth.MultiFactorUser(user2, accountInfoWithEnrolledFactors) - .toPlainObject(), - user1.multiFactor.toPlainObject()); - // user1.multiFactor user reference should keep pointing to user1. - assertEquals(user1, user1.multiFactor.getUser()); - - user1.copy(user3); - // Same reference should be kept. - assertEquals(user1.multiFactor, multiFactorUser1); - assertObjectEquals( - new fireauth.MultiFactorUser(user3, accountInfo).toPlainObject(), - user1.multiFactor.toPlainObject()); - assertEquals(0, user1.multiFactor.enrolledFactors.length); - // user1.multiFactor user reference should keep pointing to user1. - assertEquals(user1, user1.multiFactor.getUser()); -} - - -function testUser_rpcHandlerEndpoints() { - // Confirm expected endpoint config passed to underlying RPC handler. - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint, - 'identityPlatformEndpoint': endpoint.identityPlatformEndpoint - }; - stubs.replace( - fireauth.constants, - 'getEndpointConfig', - function(opt_id) { - return endpointConfig; - }); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor(config1['apiKey'], endpointConfig, ignoreArgument) - .$returns(rpcHandler); - rpcHandler.updateTenantId(null); - mockControl.$replayAll(); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); -} - - -function testUser_rpcHandlerEndpoints_tenantId() { - // Confirm expected endpoint config passed to underlying RPC handler. - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - stubs.replace( - fireauth.constants, - 'getEndpointConfig', - function(opt_id) { - return endpointConfig; - }); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor(config1['apiKey'], endpointConfig, ignoreArgument) - .$returns(rpcHandler); - // Tenant ID of RPC handler should be updated. - rpcHandler.updateTenantId('TENANT_ID'); - mockControl.$replayAll(); - // Sets the tenant ID on user. - accountInfo['tenantId'] = 'TENANT_ID'; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertEquals('TENANT_ID', user['tenantId']); -} - - -function testUser_stateChangeListeners() { - // Test user state change listeners: adding, removing and their execution. - asyncTestCase.waitForSignals(3); - var listener1 = goog.testing.recordFunction(function(userTemp) { - assertEquals(user, userTemp); - // Whether it resolves or rejects, it shouldn't affect the outcome. - return goog.Promise.resolve(); - }); - var listener2 = goog.testing.recordFunction(function(userTemp) { - assertEquals(user, userTemp); - // Whether it resolves or rejects, it shouldn't affect the outcome. - return goog.Promise.reject(); - }); - // Listener that does not return a promise. - var listener3 = goog.testing.recordFunction(); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Add all listeners. - user.addStateChangeListener(listener1); - user.addStateChangeListener(listener2); - user.addStateChangeListener(listener3); - // Notify listeners. - user.notifyStateChangeListeners_().then(function(userTemp) { - assertEquals(user, userTemp); - // All should run. - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener2.getCallCount()); - assertEquals(1, listener3.getCallCount()); - // Remove second and third listener. - user.removeStateChangeListener(listener2); - user.removeStateChangeListener(listener3); - asyncTestCase.signal(); - // Notify listeners. - user.notifyStateChangeListeners_().then(function(userTemp) { - assertEquals(user, userTemp); - // Only first listener should run. - assertEquals(2, listener1.getCallCount()); - assertEquals(1, listener2.getCallCount()); - assertEquals(1, listener3.getCallCount()); - // Remove remaining listener. - user.removeStateChangeListener(listener1); - asyncTestCase.signal(); - // Notify listeners. - user.notifyStateChangeListeners_().then(function(userTemp) { - assertEquals(user, userTemp); - // No listener should run. - assertEquals(2, listener1.getCallCount()); - assertEquals(1, listener2.getCallCount()); - assertEquals(1, listener3.getCallCount()); - asyncTestCase.signal(); - }); - }); - }); -} - - -function testGetRpcHandler() { - user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user2 = new fireauth.AuthUser(config2, tokenResponse, accountInfo); - - assertTrue(user1.getRpcHandler() instanceof fireauth.RpcHandler); - assertTrue(user2.getRpcHandler() instanceof fireauth.RpcHandler); - assertEquals(config1['apiKey'], user1.getRpcHandler().getApiKey()); - assertEquals(config2['apiKey'], user2.getRpcHandler().getApiKey()); -} - - -function testAddProviderData_sameProviderId() { - var providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'theProviderId', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - var providerData2 = new fireauth.AuthUserInfo( - 'providerUserId2', - 'theProviderId', - 'user2@example.com', - null, - 'https://www.example.com/user2/photo.png'); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - - assertArrayEquals(['theProviderId'], user.getProviderIds()); - assertArrayEquals([{ - 'uid': 'providerUserId2', - 'displayName': null, - 'photoURL': 'https://www.example.com/user2/photo.png', - 'email': 'user2@example.com', - 'providerId': 'theProviderId', - 'phoneNumber': null - }], user['providerData']); -} - - - -function testUser_removeProviderData() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - - assertArrayEquals(['providerId1', 'providerId2'], user.getProviderIds()); - user.removeProviderData('providerId1'); - assertArrayEquals(['providerId2'], user.getProviderIds()); -} - - -function testUser_setUserAccountInfoFromToken_success() { - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [ - { - 'email': 'user@gmail.com', - 'providerId': 'google.com', - 'displayName': 'John G. Doe', - 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', - 'federatedId': 'https://accounts.google.com/123456789', - 'rawId': '123456789' - }, - { - 'providerId': 'twitter.com', - 'displayName': 'John Gammell Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'federatedId': 'http://twitter.com/987654321', - 'rawId': '987654321' - } - ], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true - }); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '123456789', - 'google.com', - 'user@gmail.com', - 'John G. Doe', - 'https://lh5.googleusercontent.com/123456789/photo.jpg')); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '987654321', - 'twitter.com', - null, - 'John Gammell Doe', - 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + - '3_normal.png')); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - assertEquals(jwt, data); - resolve(response); - }); - }); - asyncTestCase.waitForSignals(1); - // Initialize user with no account info or provider data. - user = new fireauth.AuthUser(config1, tokenResponse); - // Record event triggers on USER_RELOADED. - var userReloadedEventHandler = goog.testing.recordFunction(); - goog.events.listen( - user, fireauth.UserEventType.USER_RELOADED, userReloadedEventHandler); - var stateChangedCounter = 0; - user.addStateChangeListener(function(user) { - stateChangedCounter++; - return goog.Promise.resolve(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - assertEquals(0, userReloadedEventHandler.getCallCount()); - user.reload().then(function() { - assertEquals(1, stateChangedCounter); - fireauth.common.testHelper.assertUserEquals(expectedUser, user); - // Confirm event triggered on reload with the expected properties. - assertEquals(1, userReloadedEventHandler.getCallCount()); - var event = userReloadedEventHandler.getLastCall().getArgument(0); - assertObjectEquals(response.users[0], event.userServerResponse); - asyncTestCase.signal(); - }); -} - - -function testSetUserAccountInfoFromToken_success_emailAndPassword() { - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'passwordHash': 'PASSWORD_HASH', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - assertEquals(jwt, data); - resolve(response); - }); - }); - user = new fireauth.AuthUser(config1, tokenResponse); - // Record event triggers on USER_RELOADED. - var userReloadedEventHandler = goog.testing.recordFunction(); - goog.events.listen( - user, fireauth.UserEventType.USER_RELOADED, userReloadedEventHandler); - asyncTestCase.waitForSignals(1); - assertEquals(0, userReloadedEventHandler.getCallCount()); - user.reload().then(function() { - fireauth.common.testHelper.assertUserEquals(expectedUser, user); - // Confirm event triggered on reload with the expected properties. - assertEquals(1, userReloadedEventHandler.getCallCount()); - var event = userReloadedEventHandler.getLastCall().getArgument(0); - assertObjectEquals(response.users[0], event.userServerResponse); - asyncTestCase.signal(); - }); -} - - -function testSetUserAccountInfoFromToken_success_emailNoPassword() { - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true, - 'isAnonymous': false - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - assertEquals(jwt, data); - resolve(response); - }); - }); - user = new fireauth.AuthUser(config1, tokenResponse); - asyncTestCase.waitForSignals(1); - user.reload().then(function() { - assertObjectEquals(expectedUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); -} - - -function testSetUserAccountInfoFromToken_success_passwordNoEmail() { - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': '', - 'displayName': 'John Doe', - 'passwordHash': 'PASSWORD_HASH', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': '', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'isAnonymous': false - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - assertEquals(jwt, data); - resolve(response); - }); - }); - - user = new fireauth.AuthUser(config1, tokenResponse); - asyncTestCase.waitForSignals(1); - user.reload().then(function() { - fireauth.common.testHelper.assertUserEquals(expectedUser, user); - asyncTestCase.signal(); - }); -} - - -function testUser_setUserAccountInfoFromToken_multiFactor_success() { - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [ - { - 'email': 'user@gmail.com', - 'providerId': 'google.com', - 'displayName': 'John G. Doe', - 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', - 'federatedId': 'https://accounts.google.com/123456789', - 'rawId': '123456789' - }, - { - 'providerId': 'twitter.com', - 'displayName': 'John Gammell Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'federatedId': 'http://twitter.com/987654321', - 'rawId': '987654321' - } - ], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'mfaInfo': mfaInfo - }] - }; - var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true, - 'multiFactor': multiFactor - }); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '123456789', - 'google.com', - 'user@gmail.com', - 'John G. Doe', - 'https://lh5.googleusercontent.com/123456789/photo.jpg')); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '987654321', - 'twitter.com', - null, - 'John Gammell Doe', - 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + - '3_normal.png')); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - assertEquals(jwt, data); - resolve(response); - }); - }); - asyncTestCase.waitForSignals(1); - // Initialize user with no account info or provider data. - user = new fireauth.AuthUser(config1, tokenResponse); - var multiFactorUser = user.multiFactor; - assertEquals(0, user.multiFactor.enrolledFactors.length); - // Record event triggers on USER_RELOADED. - var userReloadedEventHandler = goog.testing.recordFunction(); - goog.events.listen( - user, fireauth.UserEventType.USER_RELOADED, userReloadedEventHandler); - var stateChangedCounter = 0; - user.addStateChangeListener(function(user) { - stateChangedCounter++; - return goog.Promise.resolve(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - assertEquals(0, userReloadedEventHandler.getCallCount()); - - user.reload().then(function() { - assertEquals(1, stateChangedCounter); - fireauth.common.testHelper.assertUserEquals(expectedUser, user); - // Confirm event triggered on reload with the expected properties. - assertEquals(1, userReloadedEventHandler.getCallCount()); - var event = userReloadedEventHandler.getLastCall().getArgument(0); - assertObjectEquals(response.users[0], event.userServerResponse); - // Confirm enrolled factors updated. - assertEquals(mfaInfo.length, user.multiFactor.enrolledFactors.length); - assertObjectEquals( - fireauth.MultiFactorInfo.fromServerResponse(mfaInfo[0]), - user.multiFactor.enrolledFactors[0]); - assertObjectEquals( - fireauth.MultiFactorInfo.fromServerResponse(mfaInfo[1]), - user.multiFactor.enrolledFactors[1]); - // Multifactor reference should not be updated. - assertEquals(multiFactorUser, user.multiFactor); - asyncTestCase.signal(); - }); -} - - -function testSetUserAccountInfoFromToken_success_tenantId() { - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'passwordHash': 'PASSWORD_HASH', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + - 'default_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'tenantId': 'TENANT_ID' - }] - }; - var updateTenantId = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'updateTenantId'); - // Tenant ID of RPC handler should be initialized to null. - updateTenantId(null).$once(); - // Tenant ID of RPC handler should be updated by setAccountInfo. - updateTenantId('TENANT_ID').$once(); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - assertEquals(jwt, data); - return goog.Promise.resolve(response); - }); - mockControl.$replayAll(); - user = new fireauth.AuthUser(config1, tokenResponse); - asyncTestCase.waitForSignals(1); - user.reload().then(function() { - assertEquals('TENANT_ID', user['tenantId']); - asyncTestCase.signal(); - }); -} - - -function testUser_setUserAccountInfoFromToken_error() { - var error = { - 'error': fireauth.authenum.Error.INTERNAL_ERROR - }; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return goog.Promise.reject(error); - }); - asyncTestCase.waitForSignals(1); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.reload().thenCatch(function(e) { - // User data unchanged. - for (var key in accountInfo) { - // Metadata is structured differently in user compared to accountInfo. - if (key == 'lastLoginAt') { - assertEquals( - fireauth.util.utcTimestampToDateString(accountInfo[key]), - user['metadata']['lastSignInTime']); - } else if (key == 'createdAt') { - assertEquals( - fireauth.util.utcTimestampToDateString(accountInfo[key]), - user['metadata']['creationTime']); - } else { - assertEquals(accountInfo[key], user[key]); - } - } - assertObjectEquals(error, e); - asyncTestCase.signal(); - }); -} - - -function testUser_setUserAccountInfoFromToken_invalidResponse() { - // Test with invalid server response. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - // Resolve getAccountInfo with invalid server response. - return goog.Promise.resolve({}); - }); - asyncTestCase.waitForSignals(1); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.reload().thenCatch(function(error) { - var expected = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - assertEquals(expected.code, error.code); - // User data unchanged. - for (var key in accountInfo) { - // Metadata is structured differently in user compared to accountInfo. - if (key == 'lastLoginAt') { - assertEquals( - fireauth.util.utcTimestampToDateString(accountInfo[key]), - user['metadata']['lastSignInTime']); - } else if (key == 'createdAt') { - assertEquals( - fireauth.util.utcTimestampToDateString(accountInfo[key]), - user['metadata']['creationTime']); - } else { - assertEquals(accountInfo[key], user[key]); - } - } - asyncTestCase.signal(); - }); -} - - -function testUser_reload_success() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - user.addStateChangeListener(function(user) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - assertEquals(jwt, idToken); - user.copy(updatedUser); - asyncTestCase.signal(); - return goog.Promise.resolve(myAccountInfo); - }); - var myAccountInfo = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'new_uid123@fake.com', - 'emailVerified': true, - 'displayName': 'Fabrice', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - var updatedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'new_uid123@fake.com', - 'displayName': 'Fabrice', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true - }); - asyncTestCase.waitForSignals(3); - user.reload().then(function() { - assertObjectEquals(updatedUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); -} - - -/** - * Tests the case where a user currently stored in local storage as not - * anonymous reloads its data and has no more credential (for instance, it has - * unlinked all its providers). The isAnonymous flag should remain false. - */ -function testUser_reload_success_noCredentialUserLocallyNotAnonymous() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - user.addStateChangeListener(function(user) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': '', - 'displayName': 'John Doe', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'disabled': false - }] - }; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - assertEquals(jwt, data); - asyncTestCase.signal(); - resolve(response); - }); - }); - var updatedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': '', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'isAnonymous': false - }); - asyncTestCase.waitForSignals(3); - user.reload().then(function() { - assertObjectEquals(updatedUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); -} - - -/** - * Tests that an anonymous user remains anonymous when no credential in the - * GetAccountInfo response. - */ -function testUser_reload_success_noCredentialUserLocallyAnonymous() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - user.updateProperty('isAnonymous', true); - user.addStateChangeListener(function(user) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': '', - 'displayName': 'John Doe', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'disabled': false - }] - }; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - assertEquals(jwt, data); - asyncTestCase.signal(); - resolve(response); - }); - }); - var updatedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': '', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'isAnonymous': true - }); - asyncTestCase.waitForSignals(3); - user.reload().then(function() { - assertObjectEquals(updatedUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); -} - - -function testUser_reload_success_tenantId() { - accountInfo2['tenantId'] = 'TENANT_ID'; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - assertEquals('TENANT_ID', user['tenantId']); - user.addStateChangeListener(function(user) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - assertEquals(jwt, idToken); - user.copy(updatedUser); - asyncTestCase.signal(); - return goog.Promise.resolve(myAccountInfo); - }); - var myAccountInfo = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'new_uid123@fake.com', - 'emailVerified': true, - 'displayName': 'Fabrice', - 'providerUserInfo': [], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'tenantId': 'TENANT_ID' - }] - }; - var updatedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'new_uid123@fake.com', - 'displayName': 'Fabrice', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true, - 'tenantId': 'TENANT_ID' - }); - asyncTestCase.waitForSignals(3); - user.reload().then(function() { - assertObjectEquals(updatedUser.toPlainObject(), user.toPlainObject()); - assertEquals('TENANT_ID', user['tenantId']); - asyncTestCase.signal(); - }); -} - - -function testUser_reload_general_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - assertNoDeleteEvents(user); - asyncTestCase.waitForSignals(2); - user.reload().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_reload_userNotFound_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(3); - user.reload().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testExtractLinkedAccounts() { - var resp = { - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [ - { - 'email': 'user@gmail.com', - 'providerId': 'google.com', - 'displayName': 'John Doe', - 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', - 'federatedId': 'https://accounts.google.com/123456789', - 'rawId': '123456789' - }, - { - 'providerId': 'twitter.com', - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defa' + - 'ult_profile_3_normal.png', - 'federatedId': 'http://twitter.com/987654321', - 'rawId': '987654321' - } - ], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }; - var expectedProviders = [ - new fireauth.AuthUserInfo( - '123456789', - 'google.com', - 'user@gmail.com', - 'John Doe', - 'https://lh5.googleusercontent.com/123456789/photo.jpg'), - new fireauth.AuthUserInfo( - '987654321', - 'twitter.com', - null, - 'John Doe', - 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + - '3_normal.png')]; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertObjectEquals( - expectedProviders, - user.extractLinkedAccounts_(resp)); -} - - -function testExtractLinkedAccounts_withoutEmail() { - var resp = { - 'localId': '14584746072031976743', - 'email': '', - 'emailVerified': false, - 'displayName': 'John Doe', - 'providerUserInfo': [ - { - 'providerId': 'twitter.com', - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defa' + - 'ult_profile_3_normal.png', - 'federatedId': 'http://twitter.com/987654321', - 'rawId': '987654321' - } - ], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }; - var expectedProviders = [ - new fireauth.AuthUserInfo( - '987654321', - 'twitter.com', - null, - 'John Doe', - 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + - '3_normal.png')]; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertObjectEquals( - expectedProviders, - user.extractLinkedAccounts_(resp)); -} - - -function testUser_getIdTokenResult() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - asyncTestCase.waitForSignals(1); - // Test with available token. - stubs.replace( - fireauth.AuthUser.prototype, - 'getIdToken', - function(opt_forceRefresh) { - return goog.Promise.resolve(idTokenCustomClaims.jwt); - }); - user.getIdTokenResult().then(function(idTokenResult) { - fireauth.common.testHelper.assertIdTokenResult( - idTokenResult, - idTokenCustomClaims.jwt, - idTokenCustomClaims.data.exp, - idTokenCustomClaims.data.auth_time, - idTokenCustomClaims.data.iat, - 'password', - null, - idTokenCustomClaims.data); - asyncTestCase.signal(); - }); -} - - -function testUser_getIdTokenResult_forceRefresh() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - asyncTestCase.waitForSignals(1); - // Test with available token. - stubs.replace( - fireauth.AuthUser.prototype, - 'getIdToken', - function(opt_forceRefresh) { - assertTrue(opt_forceRefresh); - return goog.Promise.resolve(idTokenCustomClaims.jwt); - }); - user.getIdTokenResult(true).then(function(idTokenResult) { - fireauth.common.testHelper.assertIdTokenResult( - idTokenResult, - idTokenCustomClaims.jwt, - idTokenCustomClaims.data.exp, - idTokenCustomClaims.data.auth_time, - idTokenCustomClaims.data.iat, - 'password', - null, - idTokenCustomClaims.data); - asyncTestCase.signal(); - }); -} - - -function testUser_getIdTokenResult_error() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - asyncTestCase.waitForSignals(1); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - stubs.replace( - fireauth.AuthUser.prototype, - 'getIdToken', - function(opt_forceRefresh) { - return goog.Promise.reject(expectedError); - }); - user.getIdTokenResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_getIdTokenResult_invalidToken() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - asyncTestCase.waitForSignals(1); - stubs.replace( - fireauth.AuthUser.prototype, - 'getIdToken', - function(opt_forceRefresh) { - return goog.Promise.resolve('gegege.invalid.ggrgheh'); - }); - user.getIdTokenResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - 'An internal error occurred. The token obtained by Firebase '+ - 'appears to be malformed. Please retry the operation.'), - error); - asyncTestCase.signal(); - }); -} - - -function testUser_getIdTokenResult_expiredToken_reauth() { - // Test when token is expired and user is reauthenticated. - // User should be validated after, even though user invalidation event is - // triggered. - var validTokenResponse = { - 'idToken': expectedReauthenticateTokenResponse['idToken'], - 'accessToken': expectedReauthenticateTokenResponse['idToken'], - 'refreshToken': expectedReauthenticateTokenResponse['refreshToken'] - }; - var credential = /** @type {!fireauth.AuthCredential} */ ({ - matchIdTokenWithUid: function() { - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - return goog.Promise.resolve(expectedReauthenticateTokenResponse); - } - }); - // Expected token expired error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - // Event trackers. - var stateChangeCounter = 0; - var authChangeCounter = 0; - var userInvalidateCounter = 0; - accountInfo['uid'] = '679'; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Track token changes. - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - authChangeCounter++; - }); - // Track user invalidation events. - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidateCounter++; - }); - // State change should be triggered. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Stub token manager. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Resolve if new refresh token provided. - if (this.getRefreshToken() == validTokenResponse['refreshToken']) { - return goog.Promise.resolve(validTokenResponse); - } - // Reject otherwise. - return goog.Promise.reject(expectedError); - }); - // Confirm expected initial refresh token set on user. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - // Call getIdToken, it should trigger the expected error and a state change - // event. - user.getIdTokenResult().thenCatch(function(error) { - // Refresh token nullified. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - // Confirm expected error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No state change. - assertEquals(0, stateChangeCounter); - // No Auth change. - assertEquals(0, authChangeCounter); - // User invalidated change. - assertEquals(1, userInvalidateCounter); - // Call again, it should not trigger another state change or any other - // event. - user.getIdTokenResult().thenCatch(function(error) { - // Resolves with same error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No additional change. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(1, userInvalidateCounter); - // Assume user reauthenticated. - // This should resolve. - user.reauthenticateWithCredential(credential) - .then(function(result) { - // Set via reauthentication. - assertEquals( - validTokenResponse['refreshToken'], user['refreshToken']); - // Auth token change triggered. - assertEquals(1, authChangeCounter); - // State change triggers, after reauthentication. - assertEquals(1, stateChangeCounter); - // Shouldn't trigger again. - assertEquals(1, userInvalidateCounter); - // This should return cached token set via reauthentication. - user.getIdTokenResult().then(function(idTokenResult) { - // Shouldn't trigger again. - assertEquals(1, authChangeCounter); - assertEquals(1, stateChangeCounter); - assertEquals(1, userInvalidateCounter); - // Refresh token should be updated along with ID token. - assertEquals( - validTokenResponse['refreshToken'], user['refreshToken']); - fireauth.common.testHelper.assertIdTokenResult( - idTokenResult, - idTokenGmail.jwt, - idTokenGmail.data.exp, - idTokenGmail.data.auth_time, - idTokenGmail.data.iat, - 'password', - null, - idTokenGmail.data); - asyncTestCase.signal(); - }); - }); - }); - }); -} - - -function testUser_getIdToken() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - asyncTestCase.signal(); - }); - assertNoStateEvents(user); - assertNoUserInvalidatedEvents(user); - asyncTestCase.waitForSignals(2); - // Test with available token. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - return goog.Promise.resolve({ - accessToken: 'accessToken1', - refreshToken: 'refreshToken1', - expirationTime: now + 3600 * 1000 - }); - }); - user.getIdToken().then(function(stsAccessToken) { - assertEquals('accessToken1', stsAccessToken); - asyncTestCase.signal(); - }); -} - - -function testUser_getIdToken_expiredToken_reauthAfterInvalidation() { - // Test when token is expired and user is reauthenticated. - // User should be validated after, even though user invalidation event is - // triggered. - var validTokenResponse = { - 'idToken': expectedReauthenticateTokenResponse['idToken'], - 'accessToken': expectedReauthenticateTokenResponse['idToken'], - 'refreshToken': expectedReauthenticateTokenResponse['refreshToken'] - }; - var credential = /** @type {!fireauth.AuthCredential} */ ({ - matchIdTokenWithUid: function() { - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - return goog.Promise.resolve(expectedReauthenticateTokenResponse); - } - }); - // Expected token expired error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - // Event trackers. - var stateChangeCounter = 0; - var authChangeCounter = 0; - var userInvalidateCounter = 0; - accountInfo['uid'] = '679'; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Track token changes. - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - authChangeCounter++; - }); - // Track user invalidation events. - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidateCounter++; - }); - // State change should be triggered. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Stub token manager. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Resolve if new refresh token provided. - if (this.getRefreshToken() == validTokenResponse['refreshToken']) { - return goog.Promise.resolve(validTokenResponse); - } - // Reject otherwise. - return goog.Promise.reject(expectedError); - }); - // Confirm expected initial refresh token set on user. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - // Call getIdToken, it should trigger the expected error and a state change - // event. - user.getIdToken().thenCatch(function(error) { - // Refresh token nullified. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - // Confirm expected error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No state change. - assertEquals(0, stateChangeCounter); - // No Auth change. - assertEquals(0, authChangeCounter); - // User invalidated change. - assertEquals(1, userInvalidateCounter); - // Call again, it should not trigger another state change or any other - // event. - user.getIdToken().thenCatch(function(error) { - // Resolves with same error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No additional change. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(1, userInvalidateCounter); - // Assume user reauthenticated. - // This should resolve. - user.reauthenticateWithCredential(credential) - .then(function(result) { - // Set via reauthentication. - assertEquals( - validTokenResponse['refreshToken'], user['refreshToken']); - // Auth token change triggered. - assertEquals(1, authChangeCounter); - // State change triggers, after reauthentication. - assertEquals(1, stateChangeCounter); - // Shouldn't trigger again. - assertEquals(1, userInvalidateCounter); - // This should return cached token set via reauthentication. - user.getIdToken().then(function(idToken) { - // Shouldn't trigger again. - assertEquals(1, authChangeCounter); - assertEquals(1, stateChangeCounter); - assertEquals(1, userInvalidateCounter); - // Refresh token should be updated along with ID token. - assertEquals( - validTokenResponse['refreshToken'], user['refreshToken']); - assertEquals(idTokenGmail.jwt, idToken); - asyncTestCase.signal(); - }); - }); - }); - }); -} - - -function testUser_getIdToken_expiredToken_reauthWithPopupAfterInvalidation() { - // Test when token is expired and user is reauthenticated with popup. - // User should be validated after, even though user invalidation event is - // triggered. - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - var validTokenResponse = { - 'idToken': expectedReauthenticateTokenResponse['idToken'], - 'accessToken': expectedReauthenticateTokenResponse['idToken'], - 'refreshToken': expectedReauthenticateTokenResponse['refreshToken'] - }; - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Simulate Auth email credential error thrown by verifyAssertionForExisting. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - function(data) { - assertObjectEquals( - { - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - return goog.Promise.resolve(expectedReauthenticateTokenResponse); - }); - // Expected token expired error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - // Event trackers. - var stateChangeCounter = 0; - var authChangeCounter = 0; - var userInvalidateCounter = 0; - // Match the token UID. - accountInfo['uid'] = '679'; - user = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Track token changes. - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - authChangeCounter++; - }); - // Track user invalidation events. - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidateCounter++; - }); - // State change should be triggered. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Stub token manager. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Resolve if new refresh token provided. - if (this.getRefreshToken() == validTokenResponse['refreshToken']) { - return goog.Promise.resolve(validTokenResponse); - } - // Reject otherwise. - return goog.Promise.reject(expectedError); - }); - // Confirm expected initial refresh token set on user. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - var provider = new fireauth.GoogleAuthProvider(); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user.enablePopupRedirect(); - // Confirm expected initial refresh token set on user. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - // Call getIdToken, it should trigger the expected error and a state change - // event. - user.getIdToken().thenCatch(function(error) { - // Refresh token remains the same. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - // Confirm expected error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No state change. - assertEquals(0, stateChangeCounter); - // No Auth change. - assertEquals(0, authChangeCounter); - // User invalidated change. - assertEquals(1, userInvalidateCounter); - // Call again, it should not trigger another state change or any other - // event. - user.getIdToken().thenCatch(function(error) { - // Resolves with same error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No additional change. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(1, userInvalidateCounter); - // Assume user reauthenticated. - // This should resolve. - user.reauthenticateWithPopup(provider).then(function(result) { - // Set via reauthentication. - assertEquals(validTokenResponse['refreshToken'], user['refreshToken']); - // Auth token change triggered. - assertEquals(1, authChangeCounter); - // State change triggers, after reauthentication. - assertEquals(1, stateChangeCounter); - // Shouldn't trigger again. - assertEquals(1, userInvalidateCounter); - // This should return cached token set via reauthentication. - user.getIdToken().then(function(idToken) { - // Shouldn't trigger again. - assertEquals(1, authChangeCounter); - assertEquals(1, stateChangeCounter); - assertEquals(1, userInvalidateCounter); - // Refresh token should be updated along with ID token. - assertEquals( - validTokenResponse['refreshToken'], user['refreshToken']); - assertEquals(idTokenGmail.jwt, idToken); - asyncTestCase.signal(); - }); - }); - }); - }); -} - - -function testUser_getIdToken_expiredToken_reauthBeforeInvalidation() { - // Test when token is expired and user is reauthenticated before invalidation - // is detected. - var validTokenResponse = { - 'idToken': expectedReauthenticateTokenResponse['idToken'], - 'accessToken': expectedReauthenticateTokenResponse['idToken'], - 'refreshToken': expectedReauthenticateTokenResponse['refreshToken'] - }; - var credential = /** @type {!fireauth.AuthCredential} */ ({ - matchIdTokenWithUid: function() { - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - return goog.Promise.resolve(expectedReauthenticateTokenResponse); - } - }); - // Expected token expired error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - // Event trackers. - var stateChangeCounter = 0; - var authChangeCounter = 0; - var userInvalidateCounter = 0; - // Match token UID. - accountInfo['uid'] = '679'; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Track token changes. - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - authChangeCounter++; - }); - // Track user invalidation events. - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidateCounter++; - }); - // State change should be triggered. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Stub token manager. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Resolve if new refresh token provided. - if (this.getRefreshToken() == validTokenResponse['refreshToken']) { - return goog.Promise.resolve(validTokenResponse); - } - // Reject otherwise. - return goog.Promise.reject(expectedError); - }); - // Confirm expected initial refresh token set on user. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - // Call reauthenticate, this should succeed and not trigger user invalidation - // event. - user.reauthenticateWithCredential(credential) - .then(function(result) { - // Set via reauthentication. - assertEquals(validTokenResponse['refreshToken'], user['refreshToken']); - // Auth token change triggered. - assertEquals(1, authChangeCounter); - // State change triggered. - assertEquals(1, stateChangeCounter); - // Externally user should not be invalidated. - assertEquals(0, userInvalidateCounter); - // This should return cached token set via reauthentication. - user.getIdToken().then(function(idToken) { - // No additional listeners should retrigger. - assertEquals(1, authChangeCounter); - assertEquals(1, stateChangeCounter); - assertEquals(0, userInvalidateCounter); - // Refresh token should be updated along with ID token. - assertEquals( - validTokenResponse['refreshToken'], user['refreshToken']); - assertEquals(idTokenGmail.jwt, idToken); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_getIdToken_expiredToken_reauthWithPopupBeforeInvalidation() { - // Test when token is expired and user is reauthenticated with popup before - // invalidation is detected. - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - var validTokenResponse = { - 'idToken': expectedReauthenticateTokenResponse['idToken'], - 'accessToken': expectedReauthenticateTokenResponse['idToken'], - 'refreshToken': expectedReauthenticateTokenResponse['refreshToken'] - }; - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Simulate verifyAssertionForExisting returns a token with the same UID. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - function(data) { - assertObjectEquals( - { - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - return goog.Promise.resolve(expectedReauthenticateTokenResponse); - }); - // Expected token expired error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - // Event trackers. - var stateChangeCounter = 0; - var authChangeCounter = 0; - var userInvalidateCounter = 0; - accountInfo['uid'] = '679'; - user = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Track token changes. - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - authChangeCounter++; - }); - // Track user invalidation events. - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidateCounter++; - }); - // State change should be triggered. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - // Stub token manager. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Resolve if new refresh token provided. - if (this.getRefreshToken() == validTokenResponse['refreshToken']) { - return goog.Promise.resolve(validTokenResponse); - } - // Reject otherwise. - return goog.Promise.reject(expectedError); - }); - // Confirm expected initial refresh token set on user. - assertEquals(tokenResponse['refreshToken'], user['refreshToken']); - var provider = new fireauth.GoogleAuthProvider(); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user.enablePopupRedirect(); - // Call reauthenticate, this should succeed and not trigger user invalidation - // event. - user.reauthenticateWithPopup(provider).then(function(result) { - // Set via reauthentication. - assertEquals(validTokenResponse['refreshToken'], user['refreshToken']); - // Auth token change triggered. - assertEquals(1, authChangeCounter); - // State change triggered. - assertEquals(1, stateChangeCounter); - // Externally user should not be invalidated. - assertEquals(0, userInvalidateCounter); - // This should return cached token set via reauthentication. - user.getIdToken().then(function(idToken) { - // No additional listeners should retrigger. - assertEquals(1, authChangeCounter); - assertEquals(1, stateChangeCounter); - assertEquals(0, userInvalidateCounter); - // Refresh token should be updated along with ID token. - assertEquals(validTokenResponse['refreshToken'], user['refreshToken']); - assertEquals(idTokenGmail.jwt, idToken); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_getIdToken_otherError() { - // Test when any error other than expired token is returned that no state - // change or token change is triggered. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // No token change. - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - // No state change. - assertNoStateEvents(user); - asyncTestCase.waitForSignals(1); - // Test with some unexpected error. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - return goog.Promise.reject(expectedError); - }); - user.getIdToken().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_getIdToken_tokenManagerReturnsNull() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - asyncTestCase.waitForSignals(1); - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - return goog.Promise.resolve(null); - }); - user.getIdToken().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), error); - asyncTestCase.signal(); - }); -} - - - -function testUser_getIdToken_unchanged() { - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - assertNoStateEvents(user); - asyncTestCase.waitForSignals(1); - // Test with available token. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - // Token unchanged. - return goog.Promise.resolve({ - accessToken: jwt, - refreshToken: 'refreshToken' - }); - }); - user.getIdToken().then(function(stsAccessToken) { - // Token unchanged. - assertEquals(jwt, stsAccessToken); - asyncTestCase.signal(); - }); -} - - -function testUser_refreshToken() { - asyncTestCase.waitForSignals(1); - var refreshToken = 'myRefreshToken'; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - assertEquals('refreshToken', user['refreshToken']); - // Test available token with refresh token to expected one above. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - this.setRefreshToken(refreshToken); - this.setAccessToken('accessToken1', now + 3600 * 1000); - return goog.Promise.resolve({ - accessToken: 'accessToken1', - refreshToken: refreshToken, - expirationTime: now + 3600 * 1000 - }); - }); - user.getIdToken().then(function(stsAccessToken) { - assertEquals('accessToken1', stsAccessToken); - assertEquals(refreshToken, user['refreshToken']); - asyncTestCase.signal(); - }); -} - - -function testUpdateTokensIfPresent_newTokens() { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoUserInvalidatedEvents(user); - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - // Checks the tokens stored. - user.getIdToken().then(function(idToken) { - assertEquals(newJwt, idToken); - assertEquals(newJwt, user.lastAccessToken_); - asyncTestCase.signal(); - }); - }); - - user.updateTokensIfPresent({ - 'idToken': newJwt, - 'refreshToken': 'newRefreshToken' - }); -} - - -function testUpdateTokensIfPresent_identicalTokens() { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - - user.updateTokensIfPresent(tokenResponse); - user.getIdToken().then(function(idToken) { - assertEquals(tokenResponse['idToken'], idToken); - asyncTestCase.signal(); - }); -} - - -function testUpdateTokensIfPresent_noTokens() { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - - user.updateTokensIfPresent({ - 'email': 'user@default.com' - }); - user.getIdToken().then(function(idToken) { - assertEquals(tokenResponse['idToken'], idToken); - asyncTestCase.signal(); - }); -} - - -function testUpdateProperty() { - user = new fireauth.AuthUser(config1, tokenResponse, {'uid': 'userId1'}); - user.updateProperty('uid', '12345678'); - assertEquals(user['uid'], '12345678'); - user.updateProperty('uid', null); - assertEquals(user['uid'], '12345678'); - user.updateProperty('displayName', 'Jack Smith'); - assertEquals(user['displayName'], 'Jack Smith'); - user.updateProperty('photoURL', 'http://www.example.com/photo/photo.png'); - assertEquals(user['photoURL'], 'http://www.example.com/photo/photo.png'); - user.updateProperty('email', 'user@example.com'); - assertEquals(user['email'], 'user@example.com'); - user.updateProperty('isAnonymous', true); - assertEquals(true, user['isAnonymous']); - user.updateProperty('invalid', 'something'); - assertUndefined(user['invalid']); -} - - -function testUpdateEmail_success() { - asyncTestCase.waitForSignals(2); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addStateChangeListener(function(userTemp) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - - // Simulates successful rpcHandler updateEmail. - var expectedResponse = { - 'email': 'newuser@example.com' - }; - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmail', - function(idToken, newEmail) { - assertEquals(jwt, idToken); - assertEquals('newuser@example.com', newEmail); - return goog.Promise.resolve(expectedResponse); - }); - // Simulates the update by the server of the email and emailVerified - // properties. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - assertEquals(jwt, idToken); - return goog.Promise.resolve({ - 'users': [{ - 'email': 'newuser@example.com', - 'emailVerified': false - }] - }); - }); - - user.updateEmail('newuser@example.com').then(function() { - assertEquals('newuser@example.com', user['email']); - assertFalse(user['emailVerified']); - asyncTestCase.signal(); - }); -} - - -function testUpdateEmail_error() { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - - // Simulates rpcHandler updateEmail error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.USER_SIGNED_OUT); - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmail', - function(idToken, newEmail) { - assertEquals(jwt, idToken); - assertEquals('newuser@example.com', newEmail); - return goog.Promise.reject(expectedError); - }); - // User should not be reloaded. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - fail('The user should not be reloaded!'); - }); - - user.updateEmail('newuser@example.com').then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No update. - assertEquals('user@default.com', user['email']); - assertTrue(user['emailVerified']); - asyncTestCase.signal(); - }); -} - - -function testUpdateEmail_userDestroyed() { - assertFailsWhenUserIsDestroyed('updateEmail', ['newuser@example.com']); -} - - -function testUpdatePhoneNumber_success() { - var expectedResponse = { - 'idToken': newJwt, - 'refreshToken': 'myRefreshToken', - 'localId': 'myLocalId', - 'isNewUser': false, - 'phoneNumber': '+16505550101' - }; - var phoneAuthCredential = mockControl.createStrictMock( - fireauth.PhoneAuthCredential); - phoneAuthCredential.linkToIdToken(ignoreArgument, tokenResponse['idToken']) - .$once() - .$returns(goog.Promise.resolve(expectedResponse)); - - var getAccountInfoResponse = { - 'users': [{ - 'localId': 'defaultUserId', - 'displayName': 'defaultDisplayName', - 'phoneNumber': '+16505550101', - 'providerUserInfo': [{ - 'providerId': 'phone', - 'rawId': '+16505550101', - 'phoneNumber': '+16505550101' - }], - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - // New STS token should be used. - getAccountInfoByIdToken(newJwt).$returns( - goog.Promise.resolve(getAccountInfoResponse)).$once(); - - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.updatePhoneNumber(phoneAuthCredential) - .then(function() { - // User properties should be updated. - assertEquals('+16505550101', user['phoneNumber']); - assertEquals(1, user['providerData'].length); - assertObjectEquals({ - 'uid': '+16505550101', - 'displayName': null, - 'photoURL': null, - 'email': null, - 'phoneNumber': '+16505550101', - 'providerId': 'phone' - }, user['providerData'][0]); - asyncTestCase.signal(); - }); -} - - -function testUpdatePhoneNumber_error() { - asyncTestCase.waitForSignals(1); - - // Simulates error from backend. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var credential = fireauth.PhoneAuthProvider.credential('id', 'code'); - stubs.replace(credential, 'linkToIdToken', function(rpcHandler, idToken) { - assertEquals(jwt, idToken); - return goog.Promise.reject(expectedError); - }); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.updatePhoneNumber(credential).then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUpdatePhoneNumber_userDestroyed() { - var credential = fireauth.PhoneAuthProvider.credential('id', 'code'); - assertFailsWhenUserIsDestroyed('updatePhoneNumber', [credential]); -} - - -function testUser_sessionInvalidation_updatePhoneNumber_tokenExpired() { - // Test user invalidation with token expired error on - // user.updatePhoneNumber. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'updatePhoneNumber', - [ - fireauth.PhoneAuthProvider.credential('foo', 'bar') - ], - invalidationError); -} - - -function testUser_sessionInvalidation_reload_userDisabled() { - // Test user invalidation with user disabled error on user.updatePhoneNumber. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation('updatePhoneNumber', - [fireauth.PhoneAuthProvider.credential('foo', 'bar')], - invalidationError); -} - - -function testUpdatePassword_success() { - asyncTestCase.waitForSignals(2); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addStateChangeListener(function(userTemp) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - - // Simulates successful rpcHandler updatePassword. - var expectedResponse = { - 'email': 'newuser@example.com' - }; - stubs.replace( - fireauth.RpcHandler.prototype, - 'updatePassword', - function(idToken, newPassword) { - assertEquals(jwt, idToken); - assertEquals('newPassword', newPassword); - return goog.Promise.resolve(expectedResponse); - }); - // Simulates the reload. - var reloaded = 0; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - reloaded++; - assertEquals(jwt, idToken); - return goog.Promise.resolve({ - 'users': [{ - 'email': 'newuser@example.com' - }] - }); - }); - - user.updatePassword('newPassword').then(function() { - assertEquals(1, reloaded); - asyncTestCase.signal(); - }); -} - - -function testUpdatePassword_error() { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - - // Simulates rpcHandler updatePassword error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - stubs.replace( - fireauth.RpcHandler.prototype, - 'updatePassword', - function(idToken, newPassword) { - assertEquals(jwt, idToken); - assertEquals('newPassword', newPassword); - return goog.Promise.reject(expectedError); - }); - - user.updatePassword('newPassword').then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUpdatePassword_userDestroyed() { - assertFailsWhenUserIsDestroyed('updatePassword', ['newPassword']); -} - - -function testUpdateProfile_success() { - asyncTestCase.waitForSignals(2); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addStateChangeListener(function(userTemp) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - - var expectedResponse = { - 'email': 'uid123@fake.com', - 'displayName': 'Jack Smith', - 'photoUrl': 'http://www.example.com/photo/photo.png' - }; - // Ignore reload for this test. - stubs.replace( - fireauth.AuthUser.prototype, - 'reload', - function() { - return goog.Promise.resolve(); - }); - // Simulates updateProfile. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateProfile', - function(idToken, profileData) { - assertEquals(jwt, idToken); - assertObjectEquals({ - 'displayName': 'Jack Smith', - 'photoUrl': 'http://www.example.com/photo/photo.png' - }, profileData); - return goog.Promise.resolve(expectedResponse); - }); - // Records calls to updateTokensIfPresent. - stubs.replace( - user, - 'updateTokensIfPresent', - goog.testing.recordFunction()); - - user.updateProfile({ - 'displayName': 'Jack Smith', - 'photoURL': 'http://www.example.com/photo/photo.png' - }).then(function() { - assertEquals('Jack Smith', user['displayName']); - assertEquals('http://www.example.com/photo/photo.png', user['photoURL']); - assertEquals(1, user.updateTokensIfPresent.getCallCount()); - assertEquals( - expectedResponse, - user.updateTokensIfPresent.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); -} - - -function testUpdateProfile_success_withPasswordProvider() { - asyncTestCase.waitForSignals(1); - var expectedDisplayName = 'Test User'; - var expectedPhotoUrl = 'http://www.example.com/photo/photo.png'; - var expectedResponse = { - 'displayName': expectedDisplayName, - 'photoUrl': expectedPhotoUrl - }; - // Mocks updateProfile. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateProfile', - function(idToken, profileData) { - assertEquals(jwt, idToken); - assertObjectEquals({ - 'displayName': expectedDisplayName, - 'photoUrl': expectedPhotoUrl - }, profileData); - return goog.Promise.resolve(expectedResponse); - }); - var providerData1 = new fireauth.AuthUserInfo( - 'UID1', - 'google.com', - 'uid1@example.com', - null, - null); - var providerData2 = new fireauth.AuthUserInfo( - 'UID2', - 'password', - 'uid2@example.com', - null, - null); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - user.updateProfile({ - 'displayName': expectedDisplayName, - 'photoURL': expectedPhotoUrl - }).then(function() { - // Confirm top level changes. - assertEquals(user['displayName'], expectedDisplayName); - assertEquals(user['photoURL'], expectedPhotoUrl); - // Confirm update on password provider display name and photo URL. - assertEquals(2, user['providerData'].length); - assertObjectEquals( - user['providerData'][0], - { - 'uid': 'UID1', - 'displayName': null, - 'photoURL': null, - 'email': 'uid1@example.com', - 'providerId': 'google.com', - 'phoneNumber': null - }); - assertObjectEquals( - user['providerData'][1], - { - 'uid': 'UID2', - 'displayName': expectedDisplayName, - 'photoURL': expectedPhotoUrl, - 'email': 'uid2@example.com', - 'providerId': 'password', - 'phoneNumber': null - }); - asyncTestCase.signal(); - }); -} - - -function testUpdateProfile_success_withoutPasswordProvider() { - asyncTestCase.waitForSignals(1); - var expectedDisplayName = 'Test User'; - var expectedPhotoUrl = 'http://www.example.com/photo/photo.png'; - var expectedResponse = { - 'displayName': expectedDisplayName, - 'photoUrl': expectedPhotoUrl - }; - // Mocks updateProfile. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateProfile', - function(idToken, profileData) { - assertEquals(jwt, idToken); - assertObjectEquals({ - 'displayName': expectedDisplayName, - 'photoUrl': expectedPhotoUrl - }, profileData); - return goog.Promise.resolve(expectedResponse); - }); - var providerData1 = new fireauth.AuthUserInfo( - 'UID1', - 'google.com', - 'uid1@example.com', - null, - null); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - user.updateProfile({ - 'displayName': expectedDisplayName, - 'photoURL': expectedPhotoUrl - }).then(function() { - // Confirm top level changes. - assertEquals(user['displayName'], expectedDisplayName); - assertEquals(user['photoURL'], expectedPhotoUrl); - // Confirm no changes on providerData array as no password provider is - // found. - assertEquals(1, user['providerData'].length); - assertObjectEquals( - user['providerData'][0], - { - 'uid': 'UID1', - 'displayName': null, - 'photoURL': null, - 'email': 'uid1@example.com', - 'providerId': 'google.com', - 'phoneNumber': null - }); - asyncTestCase.signal(); - }); -} - - -function testUpdateProfile_emptyChange() { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - // Ensures updateProfile isn't called. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateProfile', - function(idToken, profileData) { - fail('updateProfile should not be called!'); - }); - - user.updateProfile({ - 'wrongKey': 'whatever' - }).then(function() { - asyncTestCase.signal(); - }); -} - - -function testUpdateProfile_error() { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - // Simulates updateProfile. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateProfile', - function(idToken, profileData) { - assertEquals(jwt, idToken); - assertObjectEquals({ - 'displayName': 'Jack Smith', - 'photoUrl': 'http://www.example.com/photo/photo.png' - }, profileData); - return goog.Promise.reject(expectedError); - }); - - user.updateProfile({ - 'displayName': 'Jack Smith', - 'photoURL': 'http://www.example.com/photo/photo.png' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - assertEquals(accountInfo['displayName'], user['displayName']); - assertEquals(accountInfo['photoURL'], user['photoURL']); - asyncTestCase.signal(); - }); -} - - -function testUpdateProfile_userDestroyed() { - assertFailsWhenUserIsDestroyed('updateProfile', [{ - 'displayName': 'Jack Smith', - 'photoURL': 'http://www.example.com/photo/photo.png' - }]); -} - - -function testReauthenticateAndRetrieveDataWithCredential_success() { - // Test that reauthenticateAndRetrieveDataWithCredential calls - // reauthenticateWithCredential underneath. - // Record deprecation warning calls. - stubs.replace( - fireauth.deprecation, - 'log', - goog.testing.recordFunction()); - // Stub reauthenticateWithCredential. - stubs.replace( - fireauth.AuthUser.prototype, - 'reauthenticateWithCredential', - function(cred) { - assertEquals(expectedGoogleCredential, cred); - return goog.Promise.resolve(expectedResponse); - }); - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Expected response. Only the user will be returned. - var expectedResponse = { - 'user': user, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - // reauthenticateAndRetrieveDataWithCredential using Google OAuth credential. - user.reauthenticateAndRetrieveDataWithCredential(expectedGoogleCredential) - .then(function(result) { - // Confirm expected response returned. - assertEquals(expectedResponse, result); - asyncTestCase.signal(); - }); - // Confirm warning shown. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.deprecation.log.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals( - fireauth.deprecation.Deprecations.REAUTH_WITH_CREDENTIAL, - fireauth.deprecation.log.getLastCall().getArgument(0)); -} - - -function testReauthenticateAndRetrieveDataWithCredential_error() { - // Test that reauthenticateAndRetrieveDataWithCredential calls - // reauthenticateWithCredential underneath and funnels any - // underlying error thrown. - // Record deprecation warning calls. - stubs.replace( - fireauth.deprecation, - 'log', - goog.testing.recordFunction()); - stubs.replace( - fireauth.AuthUser.prototype, - 'reauthenticateWithCredential', - function(cred) { - assertEquals(expectedGoogleCredential, cred); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(1); - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // reauthenticateAndRetrieveDataWithCredential using Google OAuth credential. - user.reauthenticateAndRetrieveDataWithCredential(expectedGoogleCredential) - .thenCatch(function(error) { - // Confirm expected error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // Confirm warning shown. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.deprecation.log.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals( - fireauth.deprecation.Deprecations.REAUTH_WITH_CREDENTIAL, - fireauth.deprecation.log.getLastCall().getArgument(0)); -} - - -function testReauthenticateWithCredential() { - var credential = /** @type {!fireauth.AuthCredential} */ ({ - matchIdTokenWithUid: function() { - return goog.Promise.resolve(expectedReauthenticateTokenResponse); - } - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - assertEquals(idTokenGmail.jwt, idToken); - return goog.Promise.resolve({ - 'users': [{ - 'localId': '679' - }] - }); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '679' - }); - user.addStateChangeListener(function(userTemp) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - asyncTestCase.signal(); - }); - assertNoUserInvalidatedEvents(user); - user.reauthenticateWithCredential(credential) - .then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.REAUTHENTICATE, - result); - - assertEquals('679', user['uid']); - return user.getIdToken(); - }) - .then(function(idToken) { - assertEquals(idTokenGmail.jwt, idToken); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(3); -} - - -function testReauthenticateWithCredential_userMismatch() { - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH); - var credential = /** @type {!fireauth.AuthCredential} */ ({ - matchIdTokenWithUid: function() { - return goog.Promise.reject(expectedError); - } - }); - var user = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '679' - }); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.reauthenticateWithCredential(credential) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testReauthenticateWithCredential_fail() { - var error = new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var credential = /** @type {!fireauth.AuthCredential} */ ({ - matchIdTokenWithUid: function() { - return goog.Promise.reject(error); - } - }); - var user = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '679' - }); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.reauthenticateWithCredential(credential) - .thenCatch(function(actualError) { - fireauth.common.testHelper.assertErrorEquals(error, actualError); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testLinkAndRetrieveDataWithCredential_success() { - // Test that linkAndRetrieveDataWithCredential calls linkWithCredential - // underneath. - // Record deprecation warning calls. - stubs.replace( - fireauth.deprecation, - 'log', - goog.testing.recordFunction()); - // Stub linkAndRetrieveDataWithCredential and confirm same response is used - // for linkWithCredential with only the user returned. - stubs.replace( - fireauth.AuthUser.prototype, - 'linkWithCredential', - function(cred) { - assertEquals(expectedGoogleCredential, cred); - return goog.Promise.resolve(expectedResponse); - }); - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Expected response. Only the user will be returned. - var expectedResponse = { - 'user': user, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - // linkAndRetrieveDataWithCredential using Google OAuth credential. - user.linkAndRetrieveDataWithCredential(expectedGoogleCredential) - .then(function(result) { - // Confirm expected response returned. - assertEquals(expectedResponse, result); - asyncTestCase.signal(); - }); - // Confirm warning shown. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.deprecation.log.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals( - fireauth.deprecation.Deprecations.LINK_WITH_CREDENTIAL, - fireauth.deprecation.log.getLastCall().getArgument(0)); -} - - -function testLinkAndRetrieveDataWithCredential_error() { - // Test that linkAndRetrieveDataWithCredential calls linkWithCredential - // underneath and funnels any underlying error thrown. - // Record deprecation warning calls. - stubs.replace( - fireauth.deprecation, - 'log', - goog.testing.recordFunction()); - stubs.replace( - fireauth.AuthUser.prototype, - 'linkWithCredential', - function(cred) { - assertEquals(expectedGoogleCredential, cred); - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(1); - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // linkAndRetrieveDataWithCredential using Google OAuth credential. - user.linkAndRetrieveDataWithCredential(expectedGoogleCredential) - .thenCatch(function(error) { - // Confirm expected error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // Confirm warning shown. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.deprecation.log.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals( - fireauth.deprecation.Deprecations.LINK_WITH_CREDENTIAL, - fireauth.deprecation.log.getLastCall().getArgument(0)); -} - - -function testLinkWithCredential_emailAndPassword() { - var email = 'me@foo.com'; - var password = 'myPassword'; - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmailAndPassword', - function(actualIdToken, actualEmail, actualPassword) { - asyncTestCase.signal(); - assertEquals(jwt, actualIdToken); - assertEquals(email, actualEmail); - assertEquals(password, actualPassword); - // No credential or additional user info returned when linking an email - // and password credential. - return goog.Promise.resolve({ - 'email': email, - 'idToken': newJwt, - 'refreshToken': 'newRefreshToken' - }); - }); - - // The updated information from the backend after linking. - var updatedUserResponse = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': email, - 'emailVerified': false, - 'providerUserInfo': [], - 'photoUrl': '', - 'passwordHash': 'myPasswordHash', - 'passwordUpdatedAt': now, - 'disabled': false - }] - }; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return goog.Promise.resolve(updatedUserResponse); - }); - - var user = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': 'defaultUserId', - 'isAnonymous': true - }); - user.addStateChangeListener(function(userTemp) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - asyncTestCase.signal(); - }); - assertNoUserInvalidatedEvents(user); - var credential = fireauth.EmailAuthProvider.credential(email, - password); - user.linkWithCredential(credential) - .then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user, - // Expected null credential returned. - null, - // Expected null additional user info. - null, - // operationType not implemented yet. - fireauth.constants.OperationType.LINK, - result); - - // Email should be updated. - assertEquals(email, user['email']); - - // Should not be anonymous - assertFalse(user['isAnonymous']); - - // Tokens should be updated. - assertEquals('newRefreshToken', user['refreshToken']); - return user.getIdToken(); - }) - .then(function(returnedToken) { - assertEquals(newJwt, returnedToken); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(4); -} - - -function testLinkWithCredential_federatedIdP() { - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(request) { - asyncTestCase.signal(); - assertEquals( - 'id_token=googleIdToken&access_token=googleAccessToken&' + - 'providerId=google.com', - request['postBody']); - assertEquals(jwt, request['idToken']); - - // Update the backend user data. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseGoogleProviderData); - // Return token response with credential and additional IdP data. - return goog.Promise.resolve(expectedTokenResponseWithIdPData); - }); - - var user = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': 'defaultUserId' - }); - user.addStateChangeListener(function(userTemp) { - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - asyncTestCase.signal(); - }); - assertNoUserInvalidatedEvents(user); - user.linkWithCredential(expectedGoogleCredential) - .then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.LINK, - result); - - // Should have Google as a provider. - assertEquals('google.com', user['providerData'][0]['providerId']); - - // Tokens should be updated. - assertEquals('newRefreshToken', user['refreshToken']); - return user.getIdToken(); - }) - .then(function(returnedToken) { - assertEquals(newJwt, returnedToken); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(4); -} - - -function testLinkWithCredential_alreadyLinked() { - // User on server has the federated provider linked already. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseGoogleProviderData); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - var error = new fireauth.AuthError( - fireauth.authenum.Error.PROVIDER_ALREADY_LINKED); - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(request) { - fail('verifyAssertionForLinking RPC should not be called.'); - }); - - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - var credential = fireauth.GoogleAuthProvider.credential(null, - 'googleAccessToken'); - user.linkWithCredential(credential) - .thenCatch(function(actualError) { - fireauth.common.testHelper.assertErrorEquals(error, actualError); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testLinkWithCredential_fail() { - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - var error = new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(request) { - return goog.Promise.reject(error); - }); - - var user = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': 'defaultUserId' - }); - var credential = fireauth.GoogleAuthProvider.credential({ - 'idToken': 'googleIdToken' - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.linkWithCredential(credential) - .thenCatch(function(actualError) { - fireauth.common.testHelper.assertErrorEquals(error, actualError); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testUnlink_success() { - // User on server has two federated providers and one phone provider linked. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseProviderData1); - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseProviderData2); - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponsePhoneAuthProviderData); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // Simulate update linked account. - stubs.replace( - fireauth.RpcHandler.prototype, - 'deleteLinkedAccounts', - function(idToken, providersToDelete) { - assertEquals(jwt, idToken); - // providerId2 should be removed from user's original providers. - assertArrayEquals(providersToDelete, ['providerId2']); - var response = { - 'email': 'user@default.com', - 'providerUserInfo': [ - {'providerId': 'providerId1'}, - {'providerId': 'phone'} - ] - }; - return goog.Promise.resolve(response); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithPhone); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - user.addProviderData(providerDataPhone); - - var userWithoutProvider2 = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithPhone); - userWithoutProvider2.addProviderData(providerData1); - userWithoutProvider2.addProviderData(providerDataPhone); - - user.addStateChangeListener(function(event) { - asyncTestCase.signal(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - - user.unlink('providerId2') - .then(function(passedUser) { - // Update user in Auth state. - assertObjectEquals(userWithoutProvider2.toPlainObject(), - user.toPlainObject()); - // Should be same instance. - assertEquals(user, passedUser); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(2); -} - - -function testUnlink_alreadyDeleted() { - // User on server has only one federated provider linked despite the local - // copy having two. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseProviderData1); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - stubs.replace( - fireauth.RpcHandler.prototype, - 'deleteLinkedAccounts', - function(idToken, providersToDelete) { - fail('deleteLinkedAccounts RPC should not be called when the linked ' + - 'account is already deleted.'); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - user.addProviderData(providerData2); - - var userWithoutProvider2 = new fireauth.AuthUser(config1, tokenResponse, - accountInfo); - userWithoutProvider2.addProviderData(providerData1); - - user.addStateChangeListener(function(event) { - asyncTestCase.signal(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - - user.unlink('providerId2') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.NO_SUCH_PROVIDER), - error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(2); -} - - -function testUnlink_failure() { - var error = new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - - // User on server has one federated provider linked. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseProviderData1); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - stubs.replace( - fireauth.RpcHandler.prototype, - 'deleteLinkedAccounts', - function(idToken, providersToDelete) { - return goog.Promise.reject(error); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.addProviderData(providerData1); - - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - - user.unlink('providerId1') - .thenCatch(function(actualError) { - fireauth.common.testHelper.assertErrorEquals(error, actualError); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testUnlink_phone() { - // User on server has a federated provider and a phone number linked. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseProviderData1); - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponsePhoneAuthProviderData); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // Simulate update linked account. - stubs.replace( - fireauth.RpcHandler.prototype, - 'deleteLinkedAccounts', - function(idToken, providersToDelete) { - assertEquals(jwt, idToken); - // phone should be removed from user's original providers. - assertArrayEquals(providersToDelete, ['phone']); - var response = { - 'email': 'user@default.com', - 'providerUserInfo': [ - {'providerId': 'providerId1'} - ] - }; - return goog.Promise.resolve(response); - }); - - var user = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithPhone); - user.addProviderData(providerData1); - user.addProviderData(providerDataPhone); - - var userWithoutPhone = new fireauth.AuthUser(config1, tokenResponse, - accountInfo); - userWithoutPhone.addProviderData(providerData1); - - user.addStateChangeListener(function(event) { - asyncTestCase.signal(); - }); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - - user.unlink(fireauth.PhoneAuthProvider['PROVIDER_ID']) - .then(function(passedUser) { - // Update user in Auth state. - assertObjectEquals(userWithoutPhone.toPlainObject(), - user.toPlainObject()); - - // Explicitly test that phone number is null. - assertNull(user['phoneNumber']); - - // Should be same instance. - assertEquals(user, passedUser); - - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(2); -} - - -function testDelete_success() { - asyncTestCase.waitForSignals(2); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - goog.events.listen( - user, fireauth.UserEventType.USER_DELETED, function(event) { - asyncTestCase.signal(); - }); - - // Simulate rpcHandler deleteAccount. - stubs.replace( - fireauth.RpcHandler.prototype, - 'deleteAccount', - function(idToken) { - assertEquals(jwt, idToken); - return goog.Promise.resolve(); - }); - // Checks that destroy is called. - stubs.replace( - user, - 'destroy', - goog.testing.recordFunction(goog.bind(user.destroy, user))); - user['delete']().then(function() { - assertEquals(1, user.destroy.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testDelete_error() { - asyncTestCase.waitForSignals(1); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - goog.events.listen( - user, fireauth.UserEventType.USER_DELETED, function(event) { - fail('Auth change listener should not trigger!'); - }); - - // Simulate rpcHandler deleteAccount. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_AUTH); - stubs.replace( - fireauth.RpcHandler.prototype, - 'deleteAccount', - function(idToken) { - assertEquals(jwt, idToken); - return goog.Promise.reject(expectedError); - }); - // Checks that destroy is not called. - stubs.replace( - user, - 'destroy', - function() { - fail('User destroy should not be called!'); - }); - user['delete']().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testDelete_userDestroyed() { - assertFailsWhenUserIsDestroyed('delete', []); -} - - -function testSendEmailVerification_success() { - // Simulate successful RpcHandler sendEmailVerification. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendEmailVerification', - function(idToken, actualActionCodeSettings) { - assertEquals(jwt, idToken); - assertObjectEquals({}, actualActionCodeSettings); - return goog.Promise.resolve('user@default.com'); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.sendEmailVerification().then(function() { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testSendEmailVerification_localCopyWrongEmail() { - var expectedEmail = 'user@email.com'; - - // The backend has the email user@email.com associated with the account. - var updatedUserResponse = { - 'users': [{ - 'localId': 'userId1', - 'email': expectedEmail, - 'emailVerified': true, - 'providerUserInfo': [], - 'photoUrl': '', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return goog.Promise.resolve(updatedUserResponse); - }); - - // The RPC should still succeed and return user@email.com. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendEmailVerification', - function(idToken, actualActionCodeSettings) { - assertEquals(jwt, idToken); - assertObjectEquals({}, actualActionCodeSettings); - return goog.Promise.resolve(expectedEmail); - }); - - // This user does not have an email. - var user = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': 'userId1' - }); - assertNull(user['email']); - user.sendEmailVerification().then(function() { - assertEquals(expectedEmail, user['email']); - asyncTestCase.signal(); - }); - - // This user has the wrong email. - var user2 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': 'userId1', - 'email': 'wrong@email.com' - }); - user2.sendEmailVerification().then(function() { - assertEquals(expectedEmail, user2['email']); - asyncTestCase.signal(); - }); - - asyncTestCase.waitForSignals(2); -} - - -function testSendEmailVerification_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - // Simulate unsuccessful RpcHandler sendEmailVerification. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendEmailVerification', - function(idToken, actualActionCodeSettings) { - assertObjectEquals({}, actualActionCodeSettings); - return goog.Promise.reject(expectedError); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.sendEmailVerification() - .then(function() { - fail('sendEmailVerification should not resolve!'); - }) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testSendEmailVerification_actionCodeSettings_success() { - // Simulate successful RpcHandler sendEmailVerification with action code - // settings. - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendEmailVerification', - function(idToken, actualActionCodeSettings) { - assertObjectEquals( - new fireauth.ActionCodeSettings(actionCodeSettings).buildRequest(), - actualActionCodeSettings); - assertEquals(jwt, idToken); - return goog.Promise.resolve('user@default.com'); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.sendEmailVerification(actionCodeSettings).then(function() { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testSendEmailVerification_actionCodeSettings_error() { - // Simulate sendEmailVerification with invalid action code settings. - var settings = { - 'url': '' - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CONTINUE_URI); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.sendEmailVerification(settings).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testVerifyBeforeUpdateEmail_success() { - var newEmail = 'newUser@example.com'; - // Simulate successful RpcHandler verifyBeforeUpdateEmail. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyBeforeUpdateEmail', - function(idToken, email, actualActionCodeSettings) { - assertEquals(jwt, idToken); - assertEquals(newEmail, email); - assertObjectEquals({}, actualActionCodeSettings); - // Returns the user's current email. - return goog.Promise.resolve('user@default.com'); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.verifyBeforeUpdateEmail(newEmail).then(function() { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testVerifyBeforeUpdateEmail_localCopyWrongEmail() { - var expectedEmail = 'user@email.com'; - var newEmail = 'newUser@example.com'; - // The backend has the email user@email.com associated with the account. - var updatedUserResponse = { - 'users': [{ - 'localId': 'userId1', - 'email': expectedEmail, - 'emailVerified': true, - 'providerUserInfo': [], - 'photoUrl': '', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return goog.Promise.resolve(updatedUserResponse); - }); - - // The RPC should still succeed and return user@email.com. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyBeforeUpdateEmail', - function(idToken, email, actualActionCodeSettings) { - assertEquals(jwt, idToken); - assertEquals(newEmail, email); - assertObjectEquals({}, actualActionCodeSettings); - return goog.Promise.resolve(expectedEmail); - }); - - // This user does not have an email. - var user = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': 'userId1' - }); - // User state change listener should be triggered on user reload. - var stateChangedCounter1 = 0; - user.addStateChangeListener(function(user) { - stateChangedCounter1++; - return goog.Promise.resolve(); - }); - assertNull(user['email']); - user.verifyBeforeUpdateEmail(newEmail).then(function() { - assertEquals(expectedEmail, user['email']); - assertEquals(1, stateChangedCounter1); - asyncTestCase.signal(); - }); - - // This user has the wrong email. - var user2 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': 'userId1', - 'email': 'wrong@email.com' - }); - // User state change listener should be triggered on user reload. - var stateChangedCounter2 = 0; - user2.addStateChangeListener(function(user) { - stateChangedCounter2++; - return goog.Promise.resolve(); - }); - user2.verifyBeforeUpdateEmail(newEmail).then(function() { - assertEquals(expectedEmail, user2['email']); - assertEquals(1, stateChangedCounter2); - asyncTestCase.signal(); - }); - - asyncTestCase.waitForSignals(2); -} - - -function testVerifyBeforeUpdateEmail_error() { - var newEmail = 'newUser@example.com'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - // Simulate unsuccessful RpcHandler verifyBeforeUpdateEmail. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyBeforeUpdateEmail', - function(idToken, email, actualActionCodeSettings) { - assertEquals(jwt, idToken); - assertEquals(newEmail, email); - assertObjectEquals({}, actualActionCodeSettings); - return goog.Promise.reject(expectedError); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.verifyBeforeUpdateEmail(newEmail) - .then(function() { - fail('verifyBeforeUpdateEmail should not resolve!'); - }) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testVerifyBeforeUpdateEmail_actionCodeSettings_success() { - var newEmail = 'newUser@example.com'; - // Simulate successful RpcHandler verifyBeforeUpdateEmail with action code - // settings. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyBeforeUpdateEmail', - function(idToken, email, actualActionCodeSettings) { - assertObjectEquals( - new fireauth.ActionCodeSettings(actionCodeSettings).buildRequest(), - actualActionCodeSettings); - assertEquals(jwt, idToken); - assertEquals(newEmail, email); - return goog.Promise.resolve('user@default.com'); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.verifyBeforeUpdateEmail(newEmail, actionCodeSettings).then(function() { - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testVerifyBeforeUpdateEmail_actionCodeSettings_error() { - var newEmail = 'newUser@example.com'; - // Simulate verifyBeforeUpdateEmail with invalid action code settings. - var settings = { - 'url': '' - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CONTINUE_URI); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - assertNoStateEvents(user); - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - user.verifyBeforeUpdateEmail(newEmail, settings).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testDestroy() { - var config = { - 'apiKey': 'apiKey1', - 'appName': 'appId1', - 'authDomain': 'subdomain.firebaseapp.com' - }; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - mockControl.$replayAll(); - // Confirm a user is subscribed to Auth event manager. - stubs.replace( - fireauth.AuthEventManager.prototype, - 'subscribe', - goog.testing.recordFunction()); - // Confirm a user is unsubscribed from Auth event manager. - stubs.replace( - fireauth.AuthEventManager.prototype, - 'unsubscribe', - goog.testing.recordFunction()); - fireauth.AuthEventManager.ENABLED = true; - var user = new fireauth.AuthUser(config, tokenResponse, accountInfo); - user.enablePopupRedirect(); - // User should be subscribed after enabling popup and redirect. - assertEquals(1, fireauth.AuthEventManager.prototype.subscribe.getCallCount()); - assertEquals( - user, - fireauth.AuthEventManager.prototype.subscribe.getLastCall(). - getArgument(0)); - assertEquals( - 0, fireauth.AuthEventManager.prototype.unsubscribe.getCallCount()); - // Destroy user. - user.destroy(); - // User should be unsubscribed from Auth event manager. - assertEquals(1, fireauth.AuthEventManager.prototype.subscribe.getCallCount()); - assertEquals( - 1, fireauth.AuthEventManager.prototype.unsubscribe.getCallCount()); - assertEquals( - user, - fireauth.AuthEventManager.prototype.unsubscribe.getLastCall(). - getArgument(0)); - assertNull(user['refreshToken']); - return user.reload() - .then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MODULE_DESTROYED), - error); - }); -} - - -function testHasSameUserIdAs() { - var user1 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '07478372139011515641', - 'displayName': 'John Doe' - }); - var user2 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid' : '07478372139011515641', - 'displayName' : 'Definitely Not John Doe' - }); - assertTrue(user1.hasSameUserIdAs(user2)); -} - - -function testHasSameUserIdAs_differentUser() { - var user1 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '07478372139011515641', - 'displayName': 'John Doe' - }); - var user2 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid' : '1231231223123112313', - 'displayName' : 'John Doe' - }); - assertFalse(user1.hasSameUserIdAs(user2)); -} - - -function testHasSameUserIdAs_noUserId() { - var user1 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': undefined - }); - var user2 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid' : '07478372139011515641' - }); - assertFalse(user1.hasSameUserIdAs(user2)); - assertFalse(user2.hasSameUserIdAs(user1)); -} - - -function testHasSameUserIdAs_falsyId() { - var user1 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': 0, - 'displayName': 'John Doe' - }); - var user2 = new fireauth.AuthUser(config1, tokenResponse, { - 'uid' : 0, - 'displayName': 'Dohn Joe' - }); - assertTrue(user1.hasSameUserIdAs(user2)); -} - - -function testUser_copy() { - var accountInfo1 = { - 'uid': 'accountUserId1', - 'email': null, - 'displayName': 'DefaultUser1', - 'photoURL': 'https://www.example.com/default1/photo.png', - 'emailVerified': true, - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt - }; - var providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - null); - var accountInfo2 = { - 'uid': 'accountUserId2', - 'email': 'user2@default.com', - 'displayName': null, - 'photoURL': null, - 'emailVerified': false, - 'isAnonymous': true, - 'lastLoginAt': lastLoginAt2, - 'createdAt': createdAt2 - }; - var providerData2 = new fireauth.AuthUserInfo( - 'providerUserId2', - 'providerId2', - 'user2@example.com', - null, - null); - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo1); - user1.addProviderData(providerData1); - var user2 = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - user1.addProviderData(providerData2); - assertObjectNotEquals(user1, user2); - user1.copy(user2); - // user1 should now be equal to user2. - fireauth.common.testHelper.assertUserEquals(user1, user2); -} - - -function testUser_toPlainObject() { - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png', - '+11234567890'); - config1['authDomain'] = 'www.example.com'; - config1['appName'] = 'appId1'; - accountInfoWithPhone['tenantId'] = 'TENANT_ID'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithPhone); - user1.addProviderData(providerData1); - // Confirm redirect event ID is added to plain object. - user1.setRedirectEventId('5678'); - assertObjectEquals( - { - 'uid': 'defaultUserId', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'email': 'user@default.com', - 'emailVerified': true, - 'phoneNumber': '+16505550101', - 'isAnonymous': false, - 'providerData': [ - { - 'uid': 'providerUserId1', - 'displayName': null, - 'photoURL': 'https://www.example.com/user1/photo.png', - 'email': 'user1@example.com', - 'providerId': 'providerId1', - 'phoneNumber': '+11234567890' - } - ], - 'apiKey': 'apiKey1', - 'authDomain': 'www.example.com', - 'appName': 'appId1', - 'stsTokenManager': { - 'apiKey': 'apiKey1', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': now + 4800 * 1000 - }, - 'redirectEventId': '5678', - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt, - 'tenantId': 'TENANT_ID', - 'multiFactor': { - 'enrolledFactors': [] - } - }, - user1.toPlainObject()); -} - - -function testUser_toPlainObject_noMetadata() { - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png', - '+11234567890'); - config1['authDomain'] = 'www.example.com'; - config1['appName'] = 'appId1'; - // Remove metadata from account info. - delete accountInfoWithPhone['lastLoginAt']; - delete accountInfoWithPhone['createdAt']; - var user1 = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithPhone); - user1.addProviderData(providerData1); - // Confirm redirect event ID is added to plain object. - user1.setRedirectEventId('5678'); - assertObjectEquals( - { - 'uid': 'defaultUserId', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'email': 'user@default.com', - 'emailVerified': true, - 'phoneNumber': '+16505550101', - 'isAnonymous': false, - 'providerData': [ - { - 'uid': 'providerUserId1', - 'displayName': null, - 'photoURL': 'https://www.example.com/user1/photo.png', - 'email': 'user1@example.com', - 'providerId': 'providerId1', - 'phoneNumber': '+11234567890' - } - ], - 'apiKey': 'apiKey1', - 'authDomain': 'www.example.com', - 'appName': 'appId1', - 'stsTokenManager': { - 'apiKey': 'apiKey1', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': now + 4800 * 1000 - }, - 'redirectEventId': '5678', - 'tenantId': null, - // Metadata should be null. - 'lastLoginAt': null, - 'createdAt': null, - 'multiFactor': { - 'enrolledFactors': [] - } - }, - user1.toPlainObject()); -} - - -function testUser_toPlainObject_enrolledFactors() { - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png', - '+11234567890'); - config1['authDomain'] = 'www.example.com'; - config1['appName'] = 'appId1'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithEnrolledFactors); - user1.addProviderData(providerData1); - // Confirm redirect event ID is added to plain object. - user1.setRedirectEventId('5678'); - assertObjectEquals( - { - 'uid': 'defaultUserId', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'email': 'user@default.com', - 'emailVerified': true, - 'isAnonymous': false, - 'phoneNumber': null, - 'providerData': [ - { - 'uid': 'providerUserId1', - 'displayName': null, - 'photoURL': 'https://www.example.com/user1/photo.png', - 'email': 'user1@example.com', - 'providerId': 'providerId1', - 'phoneNumber': '+11234567890' - } - ], - 'apiKey': 'apiKey1', - 'authDomain': 'www.example.com', - 'appName': 'appId1', - 'stsTokenManager': { - 'apiKey': 'apiKey1', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': now + 4800 * 1000 - }, - 'redirectEventId': '5678', - 'tenantId': null, - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt, - 'multiFactor': multiFactor - }, - user1.toPlainObject()); -} - - -function testToJson() { - config1['authDomain'] = 'www.example.com'; - config1['appName'] = 'appId1'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user1.addProviderData(providerData1); - assertObjectEquals(user1.toPlainObject(), user1.toJSON()); - // Make sure JSON.stringify works and uses underlying toJSON. - assertEquals(JSON.stringify(user1), JSON.stringify(user1.toPlainObject())); -} - - -function testUser_fromPlainObject() { - accountInfoWithPhone['isAnonymous'] = true; - accountInfoWithPhone['tenantId'] = 'TENANT_ID'; - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - config1['authDomain'] = 'www.example.com'; - config1['appName'] = 'appId1'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithPhone); - user1.addProviderData(providerData1); - user1.addProviderData(providerDataPhone); - // Confirm redirect event ID is populated from plain object. - user1.setRedirectEventId('5678'); - // Missing API key. - assertNull(fireauth.AuthUser.fromPlainObject(accountInfo)); - accountInfo['apiKey'] = 'apiKey1'; - // Missing STS token response. - assertNull(fireauth.AuthUser.fromPlainObject(accountInfo)); - fireauth.common.testHelper.assertUserEquals( - user1, - fireauth.AuthUser.fromPlainObject({ - 'uid': 'defaultUserId', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'email': 'user@default.com', - 'emailVerified': true, - 'phoneNumber': '+16505550101', - 'isAnonymous': true, - 'providerData': [ - { - 'uid': 'providerUserId1', - 'displayName': null, - 'photoURL': 'https://www.example.com/user1/photo.png', - 'email': 'user1@example.com', - 'providerId': 'providerId1', - 'phoneNumber': null - }, - { - 'uid': '+16505550101', - 'displayName': null, - 'photoURL': null, - 'email': null, - 'providerId': 'phone', - 'phoneNumber': '+16505550101' - } - ], - 'apiKey': 'apiKey1', - 'authDomain': 'www.example.com', - 'appName': 'appId1', - 'stsTokenManager': { - 'apiKey': 'apiKey1', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': now + 4800 * 1000 - }, - 'redirectEventId': '5678', - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt, - 'tenantId': 'TENANT_ID' - })); -} - - -function testUser_fromPlainObject_noMetadata() { - // User previously saved with older version without metadata. - accountInfoWithPhone['isAnonymous'] = true; - delete accountInfoWithPhone['lastLoginAt']; - delete accountInfoWithPhone['createdAt']; - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - config1['authDomain'] = 'www.example.com'; - config1['appName'] = 'appId1'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithPhone); - user1.addProviderData(providerData1); - user1.addProviderData(providerDataPhone); - // Confirm redirect event ID is populated from plain object. - user1.setRedirectEventId('5678'); - // Missing API key. - assertNull(fireauth.AuthUser.fromPlainObject(accountInfo)); - accountInfo['apiKey'] = 'apiKey1'; - // Missing STS token response. - assertNull(fireauth.AuthUser.fromPlainObject(accountInfo)); - fireauth.common.testHelper.assertUserEquals( - user1, - fireauth.AuthUser.fromPlainObject({ - 'uid': 'defaultUserId', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'email': 'user@default.com', - 'emailVerified': true, - 'phoneNumber': '+16505550101', - 'isAnonymous': true, - 'providerData': [ - { - 'uid': 'providerUserId1', - 'displayName': null, - 'photoURL': 'https://www.example.com/user1/photo.png', - 'email': 'user1@example.com', - 'providerId': 'providerId1', - 'phoneNumber': null - }, - { - 'uid': '+16505550101', - 'displayName': null, - 'photoURL': null, - 'email': null, - 'providerId': 'phone', - 'phoneNumber': '+16505550101' - } - ], - 'apiKey': 'apiKey1', - 'authDomain': 'www.example.com', - 'appName': 'appId1', - 'stsTokenManager': { - 'apiKey': 'apiKey1', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': now + 4800 * 1000 - }, - 'redirectEventId': '5678', - 'tenantId': null - })); -} - - -function testUser_fromPlainObject_tokenExpired() { - // This will simulate a user with an expired refresh token being loaded from - // storage. getIdToken should reject with the expected token expired error and - // should not trigger state or Auth change event. - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png', - '+11234567890'); - config1['authDomain'] = 'www.example.com'; - config1['appName'] = 'appId1'; - // Simulate the user has an expired token. - tokenResponse['refreshToken'] = null; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user1.addProviderData(providerData1); - accountInfo['apiKey'] = 'apiKey1'; - var parsedUser = fireauth.AuthUser.fromPlainObject({ - 'uid': 'defaultUserId', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'email': 'user@default.com', - 'emailVerified': true, - 'phoneNumber': null, - 'isAnonymous': false, - 'providerData': [ - { - 'uid': 'providerUserId1', - 'displayName': null, - 'photoURL': 'https://www.example.com/user1/photo.png', - 'email': 'user1@example.com', - 'providerId': 'providerId1', - 'phoneNumber': '+11234567890' - } - ], - 'apiKey': 'apiKey1', - 'authDomain': 'www.example.com', - 'appName': 'appId1', - 'stsTokenManager': { - 'apiKey': 'apiKey1', - // Expired refresh token. - 'refreshToken': null, - 'accessToken': jwt, - 'expirationTime': now + 4800 * 1000 - }, - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt - }); - // Confirm matching users with expired refresh token. - fireauth.common.testHelper.assertUserEquals(user1, parsedUser); - assertNull(parsedUser['refreshToken']); - // No state or token changes should be triggered. - assertNoStateEvents(parsedUser); - assertNoTokenEvents(parsedUser); - assertNoUserInvalidatedEvents(user1); - // Get token on parsed user should triggered expired token error with no state - // change. - parsedUser.getIdToken().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_fromPlainObject_enrolledFactors() { - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'providerId1', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - config1['authDomain'] = 'www.example.com'; - config1['appName'] = 'appId1'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, - accountInfoWithEnrolledFactors); - user1.addProviderData(providerData1); - user1.addProviderData(providerDataPhone); - // Confirm redirect event ID is populated from plain object. - user1.setRedirectEventId('5678'); - // Missing API key. - assertNull(fireauth.AuthUser.fromPlainObject(accountInfo)); - accountInfo['apiKey'] = 'apiKey1'; - // Missing STS token response. - assertNull(fireauth.AuthUser.fromPlainObject(accountInfo)); - fireauth.common.testHelper.assertUserEquals( - user1, - fireauth.AuthUser.fromPlainObject({ - 'uid': 'defaultUserId', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'email': 'user@default.com', - 'emailVerified': true, - 'isAnonymous': false, - 'providerData': [ - { - 'uid': 'providerUserId1', - 'displayName': null, - 'photoURL': 'https://www.example.com/user1/photo.png', - 'email': 'user1@example.com', - 'providerId': 'providerId1', - 'phoneNumber': null - }, - { - 'uid': '+16505550101', - 'displayName': null, - 'photoURL': null, - 'email': null, - 'providerId': 'phone', - 'phoneNumber': '+16505550101' - } - ], - 'apiKey': 'apiKey1', - 'authDomain': 'www.example.com', - 'appName': 'appId1', - 'stsTokenManager': { - 'apiKey': 'apiKey1', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': now + 4800 * 1000 - }, - 'redirectEventId': '5678', - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt, - 'multiFactor': multiFactor - })); -} - - -function testUser_initializeFromIdTokenResponse() { - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [ - { - 'email': 'user@gmail.com', - 'providerId': 'google.com', - 'displayName': 'John G. Doe', - 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', - 'federatedId': 'https://accounts.google.com/123456789', - 'rawId': '123456789' - }, - { - 'providerId': 'twitter.com', - 'displayName': 'John Gammell Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/def' + - 'ault_profile_3_normal.png', - 'federatedId': 'http://twitter.com/987654321', - 'rawId': '987654321' - } - ], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true - }); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '123456789', - 'google.com', - 'user@gmail.com', - 'John G. Doe', - 'https://lh5.googleusercontent.com/123456789/photo.jpg')); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '987654321', - 'twitter.com', - null, - 'John Gammell Doe', - 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + - '3_normal.png')); - var frameworks = ['firebaseui', 'angularfire']; - // Listen to all calls on setFramework. - stubs.replace( - fireauth.AuthUser.prototype, - 'setFramework', - goog.testing.recordFunction(fireauth.AuthUser.prototype.setFramework)); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - // Confirm setFramework called with expected parameters before - // getAccountInfo call. - assertEquals( - 1, - fireauth.AuthUser.prototype.setFramework.getCallCount()); - assertArrayEquals( - frameworks, - fireauth.AuthUser.prototype.setFramework.getLastCall() - .getArgument(0)); - assertEquals(jwt, data); - resolve(response); - }); - }); - asyncTestCase.waitForSignals(1); - assertEquals(0, fireauth.AuthUser.prototype.setFramework.getCallCount()); - fireauth.AuthUser.initializeFromIdTokenResponse( - config1, tokenResponse, null, frameworks).then(function(createdUser) { - // Confirm no additional calls on setFramework. - assertEquals( - 1, - fireauth.AuthUser.prototype.setFramework.getCallCount()); - // Confirm frameworks set on created user. - assertArrayEquals(frameworks, createdUser.getFramework()); - assertObjectEquals( - expectedUser.toPlainObject(), createdUser.toPlainObject()); - assertEquals('refreshToken', createdUser['refreshToken']); - // Confirm STS token manager instance properly created. - assertTrue( - createdUser.stsTokenManager_ instanceof fireauth.StsTokenManager); - assertEquals( - jwt, - createdUser.stsTokenManager_.accessToken_.toString()); - assertEquals( - 'refreshToken', createdUser.stsTokenManager_.refreshToken_); - asyncTestCase.signal(); - }); -} - - -function testUser_initializeFromIdTokenResponse_multiFactor() { - // GetAccountInfo response with multi-factor response. - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [ - { - 'email': 'user@gmail.com', - 'providerId': 'google.com', - 'displayName': 'John G. Doe', - 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', - 'federatedId': 'https://accounts.google.com/123456789', - 'rawId': '123456789' - }, - { - 'providerId': 'twitter.com', - 'displayName': 'John Gammell Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/def' + - 'ault_profile_3_normal.png', - 'federatedId': 'http://twitter.com/987654321', - 'rawId': '987654321' - } - ], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'mfaInfo': mfaInfo - }] - }; - var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true, - 'multiFactor': multiFactor - }); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '123456789', - 'google.com', - 'user@gmail.com', - 'John G. Doe', - 'https://lh5.googleusercontent.com/123456789/photo.jpg')); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '987654321', - 'twitter.com', - null, - 'John Gammell Doe', - 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + - '3_normal.png')); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - assertEquals(jwt, data); - return goog.Promise.resolve(response); - }); - asyncTestCase.waitForSignals(1); - fireauth.AuthUser.initializeFromIdTokenResponse(config1, tokenResponse, null) - .then(function(createdUser) { - assertObjectEquals( - expectedUser.toPlainObject(), createdUser.toPlainObject()); - asyncTestCase.signal(); - }); -} - - -function testUser_initializeFromIdTokenResponse_tenantId() { - // GetAccountInfo response with tenant ID. - var response = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [ - { - 'email': 'user@gmail.com', - 'providerId': 'google.com', - 'displayName': 'John G. Doe', - 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', - 'federatedId': 'https://accounts.google.com/123456789', - 'rawId': '123456789' - }, - { - 'providerId': 'twitter.com', - 'displayName': 'John Gammell Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/def' + - 'ault_profile_3_normal.png', - 'federatedId': 'http://twitter.com/987654321', - 'rawId': '987654321' - } - ], - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'tenantId': 'TENANT_ID' - }] - }; - var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { - 'uid': '14584746072031976743', - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + - 't_profile_3_normal.png', - 'emailVerified': true, - // Tenant ID shoud be set. - 'tenantId': 'TENANT_ID' - }); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '123456789', - 'google.com', - 'user@gmail.com', - 'John G. Doe', - 'https://lh5.googleusercontent.com/123456789/photo.jpg')); - expectedUser.addProviderData(new fireauth.AuthUserInfo( - '987654321', - 'twitter.com', - null, - 'John Gammell Doe', - 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + - '3_normal.png')); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(data) { - assertEquals(jwt, data); - return goog.Promise.resolve(response); - }); - asyncTestCase.waitForSignals(1); - fireauth.AuthUser.initializeFromIdTokenResponse(config1, tokenResponse, null) - .then(function(createdUser) { - assertObjectEquals( - expectedUser.toPlainObject(), createdUser.toPlainObject()); - assertEquals('refreshToken', createdUser['refreshToken']); - // Confirm STS token manager instance properly created. - assertTrue( - createdUser.stsTokenManager_ instanceof fireauth.StsTokenManager); - assertEquals(jwt, createdUser.stsTokenManager_.accessToken_.toString()); - assertEquals( - 'refreshToken', createdUser.stsTokenManager_.refreshToken_); - assertEquals('TENANT_ID', createdUser['tenantId']); - asyncTestCase.signal(); - }); -} - - -function testUser_authEventManager_noAuthDomain() { - // When no Auth domain provided, all popup and redirect operations should - // throw the relevant error. - fireauth.AuthEventManager.ENABLED = true; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.MISSING_AUTH_DOMAIN); - // Test no Auth domain and call getAuthEventManager. - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user1.enablePopupRedirect(); - try { - user1.getAuthEventManager(); - fail('getAuthEventManager should throw missing Auth domain error.'); - } catch (error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - } -} - - -function testUser_authEventManager_authDomainProvided() { - // Confirm getAuthEventManager when authDomain provided. - fireauth.AuthEventManager.ENABLED = true; - var expectedManager = { - 'subscribe': goog.testing.recordFunction(), - 'unsubscribe': goog.testing.recordFunction() - }; - // Return a manager with recorded subscribe and unsubscribe operations. - stubs.replace( - fireauth.AuthEventManager, - 'getManager', - function(authDomain, apiKey, appName) { - assertEquals('subdomain.firebaseapp.com', authDomain); - assertEquals('apiKey1', apiKey); - assertEquals('appId1', appName); - return expectedManager; - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect operaitons. - user1.enablePopupRedirect(); - // This should resolve with the manager instance. - assertEquals(expectedManager, user1.getAuthEventManager()); - // User should be subscribed. - assertEquals(0, expectedManager.unsubscribe.getCallCount()); - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals(user1, expectedManager.subscribe.getLastCall().getArgument(0)); - // Destroy user. - user1.destroy(); - // User should be now unsubscribed. - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals(1, expectedManager.unsubscribe.getCallCount()); - assertEquals(user1, expectedManager.unsubscribe.getLastCall().getArgument(0)); -} - - -function testUser_authEventManager_emulatorConfigProvided() { - // Confirm getAuthEventManager when authDomain provided. - fireauth.AuthEventManager.ENABLED = true; - const expectedManager = { - 'subscribe': goog.testing.recordFunction(), - 'unsubscribe': goog.testing.recordFunction() - }; - // Return a manager with recorded subscribe and unsubscribe operations. - stubs.replace( - fireauth.AuthEventManager, 'getManager', - function(authDomain, apiKey, appName, emulatorConfig) { - assertEquals('subdomain.firebaseapp.com', authDomain); - assertEquals('apiKey1', apiKey); - assertEquals('appId1', appName); - assertObjectEquals( - { - url: 'http://emulator.test:1234', - disableWarnings: false, - }, - emulatorConfig); - return expectedManager; - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - config1['emulatorConfig'] = { - url: 'http://emulator.test:1234', - disableWarnings: false, - }; - const user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect operations. - user1.enablePopupRedirect(); - // This should resolve with the manager instance. - assertEquals(expectedManager, user1.getAuthEventManager()); - // User should be subscribed. - assertEquals(0, expectedManager.unsubscribe.getCallCount()); - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals(user1, expectedManager.subscribe.getLastCall().getArgument(0)); - // Destroy user. - user1.destroy(); - // User should be now unsubscribed. - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals(1, expectedManager.unsubscribe.getCallCount()); - assertEquals(user1, expectedManager.unsubscribe.getLastCall().getArgument(0)); -} - - -function testUser_authEventManager_emulatorConfigSetLater() { - // Confirm getAuthEventManager when authDomain provided. - fireauth.AuthEventManager.ENABLED = true; - // The first manager will not have the emulator enabled. - const firstManager = { - 'subscribe': goog.testing.recordFunction(), - 'unsubscribe': goog.testing.recordFunction() - }; - // The second manager will have the emulator enabled. - const secondManager = { - 'subscribe': goog.testing.recordFunction(), - 'unsubscribe': goog.testing.recordFunction() - }; - // Return a manager with recorded subscribe and unsubscribe operations. - let getManagerCallCount = 0; - stubs.replace( - fireauth.AuthEventManager, 'getManager', - function(authDomain, apiKey, appName, emulatorConfig) { - getManagerCallCount++; - assertEquals('subdomain.firebaseapp.com', authDomain); - assertEquals('apiKey1', apiKey); - assertEquals('appId1', appName); - if (getManagerCallCount == 1) { - assertNull(emulatorConfig); - return firstManager; - } else { - assertObjectEquals( - { - url: 'http://emulator.test:1234', - disableWarnings: false, - }, - emulatorConfig); - return secondManager; - } - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - const user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect operations. - user1.enablePopupRedirect(); - user1.setEmulatorConfig({ - url: 'http://emulator.test:1234', - disableWarnings: false, - }); - // This should resolve with the manager instance. - assertEquals(secondManager, user1.getAuthEventManager()); - // User should be subscribed to the new manager. - assertEquals(1, firstManager.unsubscribe.getCallCount()); - assertEquals(1, secondManager.subscribe.getCallCount()); - assertEquals(0, secondManager.unsubscribe.getCallCount()); - assertEquals(user1, firstManager.unsubscribe.getLastCall().getArgument(0)); - assertEquals(user1, secondManager.subscribe.getLastCall().getArgument(0)); - // Destroy user. - user1.destroy(); - // User should be now unsubscribed. - assertEquals(1, secondManager.subscribe.getCallCount()); - assertEquals(1, secondManager.unsubscribe.getCallCount()); - assertEquals(user1, secondManager.unsubscribe.getLastCall().getArgument(0)); -} - - -function testUser_authEventManager_unsubscribed() { - // Test getAuthEventManager on an unsubscribed user. - fireauth.AuthEventManager.ENABLED = true; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var expectedManager = { - 'subscribe': goog.testing.recordFunction(), - 'unsubscribe': goog.testing.recordFunction() - }; - stubs.replace( - fireauth.AuthEventManager, - 'getManager', - function(authDomain, apiKey, appName) { - assertEquals('subdomain.firebaseapp.com', authDomain); - assertEquals('apiKey1', apiKey); - assertEquals('appId1', appName); - return expectedManager; - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // User not subscribed yet. This should throw an error. - try { - user1.getAuthEventManager(); - fail('getAuthEventManager should throw an error.'); - } catch (error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - } -} - - -function testUser_finishPopupAndRedirectLink_success_withoutPostBody() { - asyncTestCase.waitForSignals(5); - // This should be populated from verifyAssertionForLinking response. - var expectedCred = fireauth.GoogleAuthProvider.credential( - null, 'ACCESS_TOKEN'); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'idToken': jwt, - 'postBody': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve({ - 'idToken': newJwt, - 'refreshToken': 'newRefreshToken', - 'providerId': 'google.com', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'rawUserInfo': expectedTokenResponseWithIdPData['rawUserInfo'] - }); - }); - // Reload should be called. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - asyncTestCase.signal(); - return goog.Promise.resolve(getAccountInfoResponse); - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Token change should be triggered. - goog.events.listen( - user1, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - asyncTestCase.signal(); - }); - assertNoUserInvalidatedEvents(user1); - // Finish popup and redirect linking. - user1.finishPopupAndRedirectLink('REQUEST_URI', 'SESSION_ID', null, null) - .then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.LINK, response); - // It should have updated the tokens. - assertEquals(newJwt, user1['_lat']); - assertEquals('newRefreshToken', user1.refreshToken); - asyncTestCase.signal(); - }); -} - - -function testUser_finishPopupAndRedirectLink_success_withPostBody() { - asyncTestCase.waitForSignals(5); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'idToken': jwt, - 'postBody': 'POST_BODY' - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve(expectedSamlTokenResponseWithIdPData); - }); - // Reload should be called. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - asyncTestCase.signal(); - return goog.Promise.resolve(getAccountInfoResponse); - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Token change should be triggered. - goog.events.listen( - user1, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - asyncTestCase.signal(); - }); - assertNoUserInvalidatedEvents(user1); - // Finish popup and redirect linking. - user1.finishPopupAndRedirectLink( - 'REQUEST_URI', 'SESSION_ID', null, 'POST_BODY') - .then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, null, expectedSamlAdditionalUserInfo, - fireauth.constants.OperationType.LINK, response); - // It should have updated the tokens. - assertEquals(newJwt, user1['_lat']); - assertEquals('newRefreshToken', user1.refreshToken); - asyncTestCase.signal(); - }); -} - - -function testUser_finishPopupAndRedirectReauth_success_withoutPostBody() { - asyncTestCase.waitForSignals(5); - // This should be populated from verifyAssertion response. - var expectedCred = fireauth.GoogleAuthProvider.credential( - null, 'ACCESS_TOKEN'); - // Simulate successful RpcHandler verifyAssertionForExisting. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve({ - 'idToken': idTokenGmail.jwt, - 'accessToken': idTokenGmail.jwt, - 'refreshToken': 'newRefreshToken', - 'providerId': 'google.com', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'rawUserInfo': expectedTokenResponseWithIdPData['rawUserInfo'] - }); - }); - // Reload should be called. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - asyncTestCase.signal(); - return goog.Promise.resolve(getAccountInfoResponse); - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - // Modify accountInfo UID to match the token UID. - accountInfo['uid'] = 679; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Token change should be triggered. - goog.events.listen( - user1, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - asyncTestCase.signal(); - }); - assertNoUserInvalidatedEvents(user1); - // Finish popup and redirect reauth. - user1.finishPopupAndRedirectReauth('REQUEST_URI', 'SESSION_ID', null, null) - .then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, response); - // It should have updated the tokens. - assertEquals(idTokenGmail.jwt, user1['_lat']); - assertEquals('newRefreshToken', user1.refreshToken); - asyncTestCase.signal(); - }); -} - - -function testUser_finishPopupAndRedirectReauth_success_withPostBody() { - asyncTestCase.waitForSignals(5); - // Simulate successful RpcHandler verifyAssertionForExisting. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': 'POST_BODY', - 'tenantId': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve({ - 'idToken': idTokenSaml.jwt, - 'accessToken': idTokenSaml.jwt, - 'refreshToken': 'newRefreshToken', - 'providerId': 'saml.provider', - 'rawUserInfo': expectedSamlTokenResponseWithIdPData['rawUserInfo'] - }); - }); - // Reload should be called. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - asyncTestCase.signal(); - return goog.Promise.resolve(getAccountInfoResponse); - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - // Modify accountInfo UID to match the token UID. - accountInfo['uid'] = 679; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Token change should be triggered. - goog.events.listen( - user1, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - asyncTestCase.signal(); - }); - assertNoUserInvalidatedEvents(user1); - // Finish popup and redirect reauth. - user1.finishPopupAndRedirectReauth( - 'REQUEST_URI', 'SESSION_ID', null, 'POST_BODY') - .then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, null, expectedSamlAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, response); - // It should have updated the tokens. - assertEquals(idTokenSaml.jwt, user1['_lat']); - assertEquals('newRefreshToken', user1.refreshToken); - asyncTestCase.signal(); - }); -} - - -function testUser_finishPopupAndRedirectReauth_success_tenantId() { - // Verify that the tenant ID is passed to RPC handler and the user with tenant - // ID is returned. - asyncTestCase.waitForSignals(5); - // This should be populated from verifyAssertion response. - var expectedCred = fireauth.GoogleAuthProvider.credential( - null, 'ACCESS_TOKEN'); - var expectedTenantId = 'TENANT_ID'; - // Simulate successful RpcHandler verifyAssertionForExisting. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': expectedTenantId - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve({ - 'idToken': idTokenGmail.jwt, - 'accessToken': idTokenGmail.jwt, - 'refreshToken': 'newRefreshToken', - 'expiresIn': '3600', - 'providerId': 'google.com', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'rawUserInfo': expectedTokenResponseWithIdPData['rawUserInfo'] - }); - }); - getAccountInfoResponse['users'][0]['tenantId'] = expectedTenantId; - // Reload should be called. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - asyncTestCase.signal(); - return goog.Promise.resolve(getAccountInfoResponse); - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - // Modify accountInfo UID to match the token UID. - accountInfo['uid'] = 679; - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - tenantUser.addStateChangeListener(function(user) { - // User state change should be triggered. - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Token change should be triggered. - goog.events.listen( - tenantUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - asyncTestCase.signal(); - }); - assertNoUserInvalidatedEvents(tenantUser); - // Finish popup and redirect reauth. - tenantUser.finishPopupAndRedirectReauth( - 'REQUEST_URI', 'SESSION_ID', expectedTenantId, null) - .then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - tenantUser, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, response); - // It should have updated the tokens. - assertEquals(idTokenGmail.jwt, tenantUser['_lat']); - assertEquals('newRefreshToken', tenantUser.refreshToken); - asyncTestCase.signal(); - }); -} - - -function testUser_finishPopupAndRedirectLink_error() { - asyncTestCase.waitForSignals(2); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Simulate error in RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'idToken': jwt, - 'postBody': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.reject(expectedError); - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // No state or token changes should be triggered. - assertNoStateEvents(user1); - assertNoTokenEvents(user1); - assertNoUserInvalidatedEvents(user1); - // Finish popup and redirect linking. This should throw the same error above. - user1.finishPopupAndRedirectLink('REQUEST_URI', 'SESSION_ID', null, null) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_finishPopupAndRedirectReauth_error() { - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Simulate error in RpcHandler verifyAssertionForExisting. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - goog.testing.recordFunction(function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - return goog.Promise.reject(expectedError); - })); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // No state or token changes should be triggered. - assertNoStateEvents(user1); - assertNoTokenEvents(user1); - assertNoUserInvalidatedEvents(user1); - // Finish popup and redirect reauth. This should throw the same error above. - user1.finishPopupAndRedirectReauth('REQUEST_URI', 'SESSION_ID', null, null) - .thenCatch(function(error) { - assertEquals( - 1, - fireauth.RpcHandler.prototype.verifyAssertionForExisting - .getCallCount()); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_finishPopupAndRedirectLink_noCredential() { - asyncTestCase.waitForSignals(2); - // Test when for some reason OAuth response is not returned. - var expectedCred = null; - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(data) { - assertObjectEquals( - { - 'requestUri': 'REQUEST_URI', - 'sessionId': 'SESSION_ID', - 'idToken': jwt, - 'postBody': null - }, - data); - asyncTestCase.signal(); - return goog.Promise.resolve({ - 'idToken': newJwt, - 'refreshToken': 'newRefreshToken', - 'providerId': 'google.com', - 'rawUserInfo': expectedTokenResponseWithIdPData['rawUserInfo'] - }); - }); - // Successful linking should trigger reload. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - asyncTestCase.signal(); - return goog.Promise.resolve(getAccountInfoResponse); - }); - config1['authDomain'] = 'subdomain.firebaseapp.com'; - var user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Finish popup and redirect linking. No credential should be returned. - user1.finishPopupAndRedirectLink('REQUEST_URI', 'SESSION_ID', null, null) - .then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.LINK, response); - // It should have updated the tokens. - assertEquals( - newJwt, user1.getStsTokenManager().accessToken_.toString()); - assertEquals( - 'newRefreshToken', - user1.getStsTokenManager().refreshToken_); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithRedirect_success_unloadsOnRedirect() { - // Test successful request for link with redirect when page unloads on - // redirect. - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Redirect event ID should be saved. - assertEquals(expectedEventId, user1.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect - // event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals(user1.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(true); - mockControl.$replayAll(); - - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Link with redirect should succeed and remain pending. - user1.linkWithRedirect(expectedProvider).then(function() { - fail('LinkWithRedirect should remain pending in environment where ' + - 'OAuthSignInHandler unloads the page.'); - }); -} - - -function testUser_reauthenticateWithRedirect_success_unloadsOnRedirect() { - // Test successful request for reauth with redirect when page unloads on - // redirect. - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Redirect event ID should be saved. - assertEquals(expectedEventId, user1.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect - // event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals(user1.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(true); - mockControl.$replayAll(); - - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Reauth with redirect should succeed and remain pending. - user1.reauthenticateWithRedirect(expectedProvider).then(function() { - fail('ReauthenticateWithRedirect should remain pending in environment ' + - 'where OAuthSignInHandler unloads the page.'); - }); -} - - -function testUser_linkWithRedirect_success_unloadsOnRedirect_tenantId() { - // Test successful request for link with redirect when page unloads on - // redirect and tenant ID is set on user. - // Verify that tenant ID is passed to OAuth sign in handler. - fireauth.AuthEventManager.ENABLED = true; - var expectedTenantId = 'TENANT_ID'; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId, - actualTenantId) { - assertEquals( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Redirect event ID should be saved. - assertEquals(expectedEventId, tenantUser.getRedirectEventId()); - // User's tenant ID should be passed to OAuth handler. - assertEquals(expectedTenantId, actualTenantId); - // Redirect user should be saved in storage with correct redirect - // event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals( - tenantUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(true); - mockControl.$replayAll(); - - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - // Mock tenant ID is returned in getAccountInfo response. - getAccountInfoResponse['users'][0]['tenantId'] = expectedTenantId; - return goog.Promise.resolve(getAccountInfoResponse); - }); - - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // Create a user in tenant project scope. - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - tenantUser.setRedirectStorageManager(storageManager); - tenantUser.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // Link with redirect should succeed and remain pending. - tenantUser.linkWithRedirect(expectedProvider).then(function() { - fail('LinkWithRedirect should remain pending in environment where ' + - 'OAuthSignInHandler unloads the page.'); - }); -} - - -function testUser_reauthWithRedirect_success_unloadsOnRedirect_tenantId() { - // Test successful request for reauth with redirect when page unloads on - // redirect and tenant ID is set on user. - // Verify that tenant ID is passed to OAuth sign in handler. - fireauth.AuthEventManager.ENABLED = true; - var expectedTenantId = 'TENANT_ID'; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId, - actualTenantId) { - assertEquals( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Redirect event ID should be saved. - assertEquals(expectedEventId, tenantUser.getRedirectEventId()); - // User's tenant ID should be passed to OAuth handler. - assertEquals(expectedTenantId, actualTenantId); - // Redirect user should be saved in storage with correct redirect - // event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals( - tenantUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(true); - mockControl.$replayAll(); - - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // Create a user in tenant project scope. - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - tenantUser.setRedirectStorageManager(storageManager); - tenantUser.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // Reauth with redirect should succeed and remain pending. - tenantUser.reauthenticateWithRedirect(expectedProvider).then(function() { - fail('ReauthenticateWithRedirect should remain pending in environment ' + - 'where OAuthSignInHandler unloads the page.'); - }); -} - - -function testUser_linkWithRedirect_success_doesNotUnloadOnRedirect() { - // Test successful request for link with redirect when page does not unload - // on redirect. - fireauth.AuthEventManager.ENABLED = true; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(false); - mockControl.$replayAll(); - - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Link with redirect should succeed and resolve in this case. - user1.linkWithRedirect(expectedProvider).then(function() { - // Redirect event ID should be saved. - assertEquals(expectedEventId, user1.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals(user1.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_reauthenticateWithRedirect_success_doesNotUnloadOnRedirect() { - // Test successful request for reauth with redirect when page does not unload - // on redirect. - fireauth.AuthEventManager.ENABLED = true; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(false); - mockControl.$replayAll(); - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Reauth with redirect should succeed and resolve in this case. - user1.reauthenticateWithRedirect(expectedProvider).then(function() { - // Redirect event ID should be saved. - assertEquals(expectedEventId, user1.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals(user1.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_linkWithRedirect_success_doesNotUnloadOnRedirect_tenantId() { - // Test successful request for link with redirect when page does not unload - // on redirect and tenant ID is set on user. - // Verify that tenant ID is passed to OAuth sign in handler. - fireauth.AuthEventManager.ENABLED = true; - var expectedTenantId = 'TENANT_ID'; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId, - actualTenantId) { - assertEquals( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // User's tenant ID should be passed to OAuth handler. - assertEquals(expectedTenantId, actualTenantId); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(false); - mockControl.$replayAll(); - - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - // Mock tenant ID is returned in getAccountInfo response. - getAccountInfoResponse['users'][0]['tenantId'] = expectedTenantId; - return goog.Promise.resolve(getAccountInfoResponse); - }); - - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // Create a user in tenant project scope. - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - tenantUser.setRedirectStorageManager(storageManager); - tenantUser.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // Link with redirect should succeed and resolve in this case. - tenantUser.linkWithRedirect(expectedProvider).then(function() { - // Redirect event ID should be saved. - assertEquals(expectedEventId, tenantUser.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals(tenantUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_reauthWithRedirect_success_doesNotUnload_tenantId() { - // Test successful request for reauth with redirect when page does not unload - // on redirect and tenant ID is set on user. - // Verify that tenant ID is passed to OAuth sign in handler. - var expectedTenantId = 'TENANT_ID'; - fireauth.AuthEventManager.ENABLED = true; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId, - actualTenantId) { - assertEquals( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // User's tenant ID should be passed to OAuth handler. - assertEquals(expectedTenantId, actualTenantId); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(false); - mockControl.$replayAll(); - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // Create a user in tenant project scope. - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - tenantUser.setRedirectStorageManager(storageManager); - tenantUser.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // Reauth with redirect should succeed and resolve in this case. - tenantUser.reauthenticateWithRedirect(expectedProvider).then(function() { - // Redirect event ID should be saved. - assertEquals(expectedEventId, tenantUser.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect event ID. - storageManager.getRedirectUser().then(function(user) { - assertEquals(expectedEventId, user.getRedirectEventId()); - assertObjectEquals(tenantUser.toPlainObject(), user.toPlainObject()); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_linkWithRedirect_success_noStorageManager() { - // Test when no storage manager supplied. - fireauth.AuthEventManager.ENABLED = true; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Redirect event ID should be saved. - assertEquals(expectedEventId, user1.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect - // event ID. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(true); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Link with redirect should never resolve. - user1.linkWithRedirect(expectedProvider).then(function() { - fail('LinkWithRedirect should remain pending in environment where ' + - 'OAuthSignInHandler unloads the page.'); - }); -} - - -function testUser_reauthenticateWithRedirect_success_noStorageManager() { - // Test when no storage manager supplied. - fireauth.AuthEventManager.ENABLED = true; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Redirect event ID should be saved. - assertEquals(expectedEventId, user1.getRedirectEventId()); - // Redirect user should be saved in storage with correct redirect - // event ID. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(true); - mockControl.$replayAll(); - var expectedEventId = '1234'; - asyncTestCase.waitForSignals(2); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // An event ID should be generated. - return expectedEventId; - }); - var expectedProvider = new fireauth.GoogleAuthProvider(); - expectedProvider.addScope('scope1'); - expectedProvider.addScope('scope2'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - user1.addStateChangeListener(function(user) { - // User state change should be triggered. - // Redirect event ID should be saved. - assertEquals(expectedEventId, user.getRedirectEventId()); - asyncTestCase.signal(); - return goog.Promise.resolve(); - }); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Reauth with redirect should never resolve. - user1.reauthenticateWithRedirect(expectedProvider).then(function() { - fail('ReauthenticateWithRedirect should remain pending in environment ' + - 'where OAuthSignInHandler unloads the page.'); - }); -} - - -function testLinkWithRedirect_missingAuthDomain() { - // Link with redirect should fail when Auth domain is missing. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(2); - - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - var provider = new fireauth.GoogleAuthProvider(); - var config = { - 'apiKey': 'apiKey1', - 'appName': 'appId1' - }; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - user1.enablePopupRedirect(); - // linkWithRedirect should fail with missing Auth domain error. - user1.linkWithRedirect(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_AUTH_DOMAIN), - error); - // Redirect user should not be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testReauthenticateWithRedirect_missingAuthDomain() { - // Reauth with redirect should fail when Auth domain is missing. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(1); - - var provider = new fireauth.GoogleAuthProvider(); - var config = { - 'apiKey': 'apiKey1', - 'appName': 'appId1' - }; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - user1.enablePopupRedirect(); - // reauthenticateWithRedirect should fail with missing Auth domain error. - user1.reauthenticateWithRedirect(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_AUTH_DOMAIN), - error); - // Redirect user should not be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_linkWithRedirect_invalidProvider() { - // Link with redirect should fail when provider is an OAuth provider. - fireauth.AuthEventManager.ENABLED = true; - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processRedirect( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualMode, - actualProvider, - actualEventId) { - assertEquals( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, actualMode); - assertEquals(provider, actualProvider); - return goog.Promise.reject(expectedError); - }); - mockControl.$replayAll(); - asyncTestCase.waitForSignals(2); - - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - // Email and password Auth provider. - var provider = new fireauth.EmailAuthProvider(); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enabled popup and redirect. - user1.enablePopupRedirect(); - // linkWithRedirect should fail with an invalid OAuth provider error. - user1.linkWithRedirect(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Redirect user should not be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithRedirect_invalidProvider() { - // Reauth with redirect should fail when provider is not an OAuth provider. - fireauth.AuthEventManager.ENABLED = true; - asyncTestCase.waitForSignals(1); - - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - - // Email and password Auth provider. - var provider = new fireauth.EmailAuthProvider(); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enabled popup and redirect. - user1.enablePopupRedirect(); - // reauthenticateWithRedirect should fail with an invalid OAuth provider - // error. - user1.reauthenticateWithRedirect(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Redirect user should not be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_linkWithRedirect_alreadyLinked() { - // User on server has the federated provider linked already. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseGoogleProviderData); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.linkWithRedirect(new fireauth.GoogleAuthProvider()) - .thenCatch(function(actualError) { - fireauth.common.testHelper.assertErrorEquals(new fireauth.AuthError( - fireauth.authenum.Error.PROVIDER_ALREADY_LINKED), actualError); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testUser_linkWithPopup_success_slowIframeEmbed() { - // Test successful link with popup with delay in embedding the iframe. - asyncTestCase.waitForSignals(3); - var clock = new goog.testing.MockClock(true); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - // Simulate popup closed. - expectedPopup.closed = true; - // Simulate the iframe took a while to embed. This should not - // trigger a popup timeout. - clock.tick(10000); - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful link via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Simulate successful RpcHandler verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(data) { - // Now that popup timer is cleared, a delay in verify assertion should - // not trigger popup closed error. - clock.tick(10000); - // Resolve with expected token response. - return goog.Promise.resolve(expectedTokenResponseWithIdPData); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // linkWithPopup should not trigger popup closed error and should resolve - // successfully. - user1.linkWithPopup(provider).then(function(popupResult) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user1, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.LINK, - popupResult); - goog.dispose(clock); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_success_slowIframeEmbed() { - // Test successful reauth with popup with delay in embedding the iframe. - asyncTestCase.waitForSignals(1); - var clock = new goog.testing.MockClock(true); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - // Simulate popup closed. - expectedPopup.closed = true; - // Simulate the iframe took a while to embed. This should not - // trigger a popup timeout. - clock.tick(10000); - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Stub getAccountInfoByIdToken which is called on reload. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Simulate successful RpcHandler verifyAssertionForExisting. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - function(data) { - // Now that popup timer is cleared, a delay in verify assertion should - // not trigger popup closed error. - clock.tick(10000); - // Resolve with expected token response. - return goog.Promise.resolve(expectedReauthenticateTokenResponse); - }); - accountInfo['uid'] = '679'; - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // reauthenticateWithPopup should not trigger popup closed error and should - // resolve successfully. - user1.reauthenticateWithPopup(provider).then(function(popupResult) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user1, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - // operationType not implemented yet. - fireauth.constants.OperationType.REAUTHENTICATE, - popupResult); - goog.dispose(clock); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_error_popupClosed() { - // Test when the popup is closed without completing sign in that the expected - // popup closed error is triggered. - asyncTestCase.waitForSignals(3); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - // Trigger popup closed by user error. - onError(expectedError); - // This should be ignored. - recordedHandler(delayedPopupAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // Expected popup closed error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.POPUP_CLOSED_BY_USER); - // The expected popup event ID. - var expectedEventId = '1234'; - // Delayed expected popup Auth event. - var delayedPopupAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // linkWithPopup should fail with the popup closed error. - user1.linkWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_error_popupClosed() { - // Test when the popup is closed without completing sign in that the expected - // popup closed error is triggered. - asyncTestCase.waitForSignals(1); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - // Trigger popup closed by user error. - onError(expectedError); - // This should be ignored. - recordedHandler(delayedPopupAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // Expected popup closed error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.POPUP_CLOSED_BY_USER); - // The expected popup event ID. - var expectedEventId = '1234'; - // Delayed expected popup Auth event. - var delayedPopupAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - goog.testing.recordFunction(function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - })); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - goog.testing.recordFunction(function(win) { - assertEquals(expectedPopup, win); - })); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // reauthenticateWithPopup should fail with the popup closed error. - user1.reauthenticateWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_error_iframeWebStorageNotSupported() { - // Test when the web storage is not supported in the iframe. - asyncTestCase.waitForSignals(1); - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.iframeclient.IfcHandler); - mockControl.createConstructorMock(fireauth.iframeclient, 'IfcHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - // Trigger web storage not supported error. - onError(expectedError); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // Expected web storage not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - // The expected popup event ID. - var expectedEventId = '1234'; - // Keep track when the popup is closed. - var isClosed = false; - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Record when the popup is closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - isClosed = true; - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // linkWithPopup should fail with the web storage no supported error. - user1.linkWithPopup(provider).thenCatch(function(error) { - // Popup should be closed. - assertTrue(isClosed); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthWithPopup_error_iframeWebStorageNotSupported() { - // Test when the web storage is not supported in the iframe. - asyncTestCase.waitForSignals(1); - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.iframeclient.IfcHandler); - mockControl.createConstructorMock(fireauth.iframeclient, 'IfcHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - // Trigger web storage not supported error. - onError(expectedError); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // Expected web storage not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - // The expected popup event ID. - var expectedEventId = '1234'; - // Keep track when the popup is closed. - var isClosed = false; - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Record when the popup is closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - isClosed = true; - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // reauthenticateWithPopup should fail with the web storage no supported - // error. - user1.reauthenticateWithPopup(provider).thenCatch(function(error) { - // Popup should be closed. - assertTrue(isClosed); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_success_withoutPostBody() { - asyncTestCase.waitForSignals(3); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful link via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - var provider = new fireauth.GoogleAuthProvider(); - // linkWithPopup should succeed with the expected popup result. - user1.linkWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_success_withPostBody() { - asyncTestCase.waitForSignals(3); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful link via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/callback', - 'SESSION_ID', - null, - 'POST_BODY'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertNull(width); - assertNull(height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': expectedSamlAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - var provider = new fireauth.SAMLAuthProvider('saml.provider'); - // linkWithPopup should succeed with the expected popup result. - user1.linkWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_success_tenantId() { - // Test successful request for link with pop when tenant ID is set on user. - // Verify that tenant ID is passed to OAuth sign in handler and - // finishPopupAndRedirectLink is called with expected tenantId. - asyncTestCase.waitForSignals(3); - var expectedTenantId = 'TENANT_ID'; - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected, - actualTenantId) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - assertEquals(expectedTenantId, actualTenantId); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - // Mock tenant ID is returned in getAccountInfo response. - getAccountInfoResponse['users'][0]['tenantId'] = expectedTenantId; - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful link via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - // Create a user in tenant project scope. - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - tenantUser.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': tenantUser, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - var provider = new fireauth.GoogleAuthProvider(); - // linkWithPopup should succeed with the expected popup result. - tenantUser.linkWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_success_withoutPostBody() { - asyncTestCase.waitForSignals(1); - - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - goog.testing.recordFunction(function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - })); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - goog.testing.recordFunction(function(win) { - assertEquals(expectedPopup, win); - })); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - // Add Google as linked provider to confirm that reauth does not fail like - // linking does when called with an already linked provider. - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'google.com', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - user1.addProviderData(providerData1); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - var provider = new fireauth.GoogleAuthProvider(); - // reauthenticateWithPopup should succeed with the expected popup result. - user1.reauthenticateWithPopup(provider).then(function(popupResult) { - // Confirm popup and closeWindow called in the process. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.popup.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.closeWindow.getCallCount()); - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_reauthenticateWithPopup_success_withPostBody() { - asyncTestCase.waitForSignals(1); - - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/callback', - 'SESSION_ID', - null, - 'POST_BODY'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - goog.testing.recordFunction(function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertNull(width); - assertNull(height); - return expectedPopup; - })); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - goog.testing.recordFunction(function(win) { - assertEquals(expectedPopup, win); - })); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - // Add Google as linked provider to confirm that reauth does not fail like - // linking does when called with an already linked provider. - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'google.com', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - user1.addProviderData(providerData1); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': null, - 'additionalUserInfo': expectedSamlAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - var provider = new fireauth.SAMLAuthProvider('saml.provider'); - // reauthenticateWithPopup should succeed with the expected popup result. - user1.reauthenticateWithPopup(provider).then(function(popupResult) { - // Confirm popup and closeWindow called in the process. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.popup.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.closeWindow.getCallCount()); - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_linkWithPopup_emailCredentialError() { - // Test when link with popup verifyAssertion throws an Auth email credential - // error. - asyncTestCase.waitForSignals(1); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - var credential = - fireauth.GoogleAuthProvider.credential({'accessToken': 'ACCESS_TOKEN'}); - // Expected Auth email credential error. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential - }); - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful link via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Simulate Auth email credential error thrown by verifyAssertion. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForLinking', - function(data) { - assertObjectEquals( - { - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'idToken': jwt, - 'postBody': null - }, - data); - return goog.Promise.reject(expectedError); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // linkWithPopup should fail with the expected Auth email credential error. - user1.linkWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_userMismatchError() { - // Test when reauth with popup verifyAssertion returns an ID token with a - // different user ID. - asyncTestCase.waitForSignals(1); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // Expected Auth email credential error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.USER_MISMATCH); - // This response will contain an ID token with a UID that does not match the - // current user's UID. - var expectedUserMismatchResponse = { - 'idToken': idTokenGmail.jwt, - 'accessToken': idTokenGmail.jwt, - 'refreshToken': 'REFRESH_TOKEN', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Simulate verifyAssertionForExisting returns a token with a different UID. - stubs.replace( - fireauth.RpcHandler.prototype, - 'verifyAssertionForExisting', - function(data) { - assertObjectEquals( - { - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }, - data); - return goog.Promise.resolve(expectedUserMismatchResponse); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - // Set redirect storage manager. - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // reauthenticateWithPopup should fail with the user mismatch error. - user1.reauthenticateWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_unsupportedEnvironment() { - // Test linkWithPopup in unsupported environment. - // Simulate popup and redirect not supported in current environment. - stubs.replace( - fireauth.util, - 'isPopupRedirectSupported', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.linkWithPopup(new fireauth.GoogleAuthProvider()) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_unsupportedEnvironment() { - // Test reauthenticateWithPopup in unsupported environment. - // Simulate popup and redirect not supported in current environment. - stubs.replace( - fireauth.util, - 'isPopupRedirectSupported', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.reauthenticateWithPopup(new fireauth.GoogleAuthProvider()) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithRedirect_unsupportedEnvironment() { - // Test linkWithRedirect in unsupported environment. - // Simulate popup and redirect not supported in current environment. - stubs.replace( - fireauth.util, - 'isPopupRedirectSupported', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.linkWithRedirect(new fireauth.GoogleAuthProvider()) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithRedirect_unsupportedEnvironment() { - // Test reauthenticateWithRedirect in unsupported environment. - // Simulate popup and redirect not supported in current environment. - stubs.replace( - fireauth.util, - 'isPopupRedirectSupported', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.reauthenticateWithRedirect(new fireauth.GoogleAuthProvider()) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_success_cannotRunInBackground() { - asyncTestCase.waitForSignals(4); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful link via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], - config['apiKey'], - config['appName'], - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - // linkWithPopup should succeed with the expected popup result. - user1.linkWithPopup(expectedProvider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_success_cannotRunInBackground_tenantId() { - asyncTestCase.waitForSignals(4); - var expectedTenantId = 'TENANT_ID'; - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected, - actualTenantId) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - assertEquals(expectedTenantId, actualTenantId); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - // Mock tenant ID is returned in getAccountInfo response. - getAccountInfoResponse['users'][0]['tenantId'] = expectedTenantId; - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful link via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], - config['apiKey'], - config['appName'], - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION, - null, - null, - expectedTenantId); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId, - clientVerison, additionalParams, endpointId, tenantId) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - assertEquals(expectedTenantId, tenantId); - return expectedUrl; - }); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - // Set tenant ID on user. - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - tenantUser.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': tenantUser, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - // linkWithPopup should succeed with the expected popup result. - tenantUser.linkWithPopup(expectedProvider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_success_cannotRunInBackground_emulatorConfig() { - asyncTestCase.waitForSignals(4); - let recordedHandler = null; - // Mock OAuth sign in handler. - const oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - const instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument) - .$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance - .processPopup( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function( - actualPopupWin, actualMode, actualProvider, actualOnInit, - actualOnError, actualEventId, actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance - .startPopupTimeout(ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - const expectedPopup = {'close': function() {}}; - // The expected popup event ID. - const expectedEventId = '1234'; - // The expected successful link via popup Auth event. - const expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, expectedEventId, - 'http://www.example.com/#response', 'SESSION_ID'); - const expectedEmulatorConfig = { - url: 'http://emulator.test:1234', - disableWarnings: false, - }; - const config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1', - 'emulatorConfig': expectedEmulatorConfig, - }; - const expectedProvider = new fireauth.GoogleAuthProvider(); - const expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], config['apiKey'], config['appName'], - fireauth.AuthEvent.Type.LINK_VIA_POPUP, expectedProvider, null, - expectedEventId, firebase.SDK_VERSION, null, null, null, - expectedEmulatorConfig); - // Simulate tab cannot run in background. - stubs.replace(fireauth.util, 'runsInBackground', function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace(fireauth.util, 'generateRandomString', function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace(fireauth.util, 'popup', function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace(fireauth.util, 'closeWindow', function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace(fireauth.util, 'generateEventId', function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, 'getOAuthHelperWidgetUrl', - function( - domain, apiKey, name, mode, provider, url, eventId, clientVerison, - additionalParams, endpointId, tenantId, emulatorConfig) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - assertObjectEquals(expectedEmulatorConfig, emulatorConfig); - return expectedUrl; - }); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - const user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - const expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - // linkWithPopup should succeed with the expected popup result. - user1.linkWithPopup(expectedProvider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_success_cannotRunInBackground() { - asyncTestCase.waitForSignals(1); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], - config['apiKey'], - config['appName'], - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - // reauthenticateWithPopup should succeed with the expected popup result. - user1.reauthenticateWithPopup(expectedProvider) - .then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_reauthenticateWithPopup_success_cannotRunInBkg_tenantId() { - asyncTestCase.waitForSignals(1); - var expectedTenantId = 'TENANT_ID'; - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected, - actualTenantId) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - assertEquals(expectedTenantId, actualTenantId); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], - config['apiKey'], - config['appName'], - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION, - null, - null, - expectedTenantId); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId, - clientVerison, additionalParams, endpointId, tenantId) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - assertEquals(expectedTenantId, tenantId); - return expectedUrl; - }); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - tenantUser.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': tenantUser, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - // reauthenticateWithPopup should succeed with the expected popup result. - tenantUser.reauthenticateWithPopup(expectedProvider) - .then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_reauthenticateWithPopup_success_cannotRunInBkg_emuConfig() { - asyncTestCase.waitForSignals(1); - let recordedHandler = null; - // Mock OAuth sign in handler. - const oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - const instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument) - .$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance - .processPopup( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function( - actualPopupWin, actualMode, actualProvider, actualOnInit, - actualOnError, actualEventId, actualAlreadyRedirected, - actualTenantId) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance - .startPopupTimeout(ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - const expectedPopup = {'close': function() {}}; - // The expected popup event ID. - const expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - const expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, expectedEventId, - 'http://www.example.com/#response', 'SESSION_ID', null, null, null); - const expectedEmulatorConfig = { - url: 'http://emulator.test:1234', - disableWarnings: false, - }; - const config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1', - 'emulatorConfig': expectedEmulatorConfig - }; - const expectedProvider = new fireauth.GoogleAuthProvider(); - const expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], config['apiKey'], config['appName'], - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, expectedProvider, null, - expectedEventId, firebase.SDK_VERSION, null, null, null, - expectedEmulatorConfig); - // Simulate tab cannot run in background. - stubs.replace(fireauth.util, 'runsInBackground', function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace(fireauth.util, 'generateRandomString', function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace(fireauth.util, 'popup', function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace(fireauth.util, 'closeWindow', function(win) { - assertEquals(expectedPopup, win); - }); - stubs.replace(fireauth.util, 'generateEventId', function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, 'getOAuthHelperWidgetUrl', - function( - domain, apiKey, name, mode, provider, url, eventId, clientVerison, - additionalParams, endpointId, tenantId, emulatorConfig) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - assertObjectEquals(expectedEmulatorConfig, emulatorConfig); - return expectedUrl; - }); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - const user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - const expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - // reauthenticateWithPopup should succeed with the expected popup result. - user1.reauthenticateWithPopup(expectedProvider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_linkWithPopup_success_iframeCanRunInBackground() { - // Test successful link with popup when tab can run in background but is an - // iframe. This should behave the same as the - // testUser_linkWithPopup_success_cannotRunInBackground test. - asyncTestCase.waitForSignals(4); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Should already be redirected. - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful link via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], - config['apiKey'], - config['appName'], - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab can run in the background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - // Simulate app is running in an iframe. This should open the popup with the - // OAuth helper redirect directly. No additional redirect is needed as it - // could be blocked due to iframe sandboxing settings. - stubs.replace( - fireauth.util, - 'isIframe', - function() { - return true; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - // linkWithPopup should succeed with the expected popup result. - user1.linkWithPopup(expectedProvider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_success_iframeCanRunInBackground() { - // Test successful reauth with popup when tab can run in background but is an - // iframe. This should behave the same as the - // testUser_reauthenticateWithPopup_success_cannotRunInBackground test. - asyncTestCase.waitForSignals(1); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - // Should already be redirected. - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], - config['apiKey'], - config['appName'], - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab can run in the background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - // Simulate app is running in an iframe. This should open the popup with the - // OAuth helper redirect directly. No additional redirect is needed as it - // could be blocked due to iframe sandboxing settings. - stubs.replace( - fireauth.util, - 'isIframe', - function() { - return true; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // The expected popup result should be returned. - return goog.Promise.resolve(expectedPopupResult); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - // reauthenticateWithPopup should succeed with the expected popup result. - user1.reauthenticateWithPopup(expectedProvider) - .then(function(popupResult) { - assertObjectEquals(expectedPopupResult, popupResult); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_linkWithPopup_webStorageUnsupported_cannotRunInBackground() { - // Test link with popup when the web storage is not supported in the iframe - // and the tab cannot run in background. - asyncTestCase.waitForSignals(1); - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - onError(expectedError); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - // Keep track when the popup is closed. - var isClosed = false; - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // Expected web storage not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], - config['apiKey'], - config['appName'], - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Check when the popup will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - isClosed = true; - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // linkWithPopup should reject with the expected error. - user1.linkWithPopup(expectedProvider).thenCatch(function(error) { - // Popup should be closed. - assertTrue(isClosed); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_reauthWithPopup_webStorageUnsupported_cantRunInBackground() { - // Test reauth with popup when the web storage is not supported in the iframe - // and the tab cannot run in background. - asyncTestCase.waitForSignals(1); - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.iframeclient.IfcHandler); - mockControl.createConstructorMock(fireauth.iframeclient, 'IfcHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(expectedProvider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertTrue(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - onError(expectedError); - return goog.Promise.resolve(); - }); - mockControl.$replayAll(); - // Keep track when the popup is closed. - var isClosed = false; - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // Expected web storage not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var expectedProvider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - config['authDomain'], - config['apiKey'], - config['appName'], - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedProvider, - null, - expectedEventId, - firebase.SDK_VERSION); - // Simulate tab cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Destination URL popped directly without the second redirect. - assertEquals(expectedUrl, url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Check when the popup will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - isClosed = true; - assertEquals(expectedPopup, win); - }); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - // Reset static getOAuthHelperWidgetUrl method on IfcHandler. - stubs.set( - fireauth.iframeclient.IfcHandler, - 'getOAuthHelperWidgetUrl', - function(domain, apiKey, name, mode, provider, url, eventId) { - assertEquals(config['authDomain'], domain); - assertEquals(config['apiKey'], apiKey); - assertEquals(config['appName'], name); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, mode); - assertEquals(expectedProvider, provider); - assertNull(url); - assertEquals(expectedEventId, eventId); - return expectedUrl; - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Set redirect storage manager. - storageManager = new fireauth.storage.RedirectUserManager( - fireauth.util.createStorageKey(config['apiKey'], config['appName'])); - user1.setRedirectStorageManager(storageManager); - // Enable popup and redirect. - user1.enablePopupRedirect(); - // reauthenticateWithPopup should reject with the expected error. - user1.reauthenticateWithPopup(expectedProvider) - .thenCatch(function(error) { - // Popup should be closed. - assertTrue(isClosed); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Popup user should never be saved in storage. - storageManager.getRedirectUser().then(function(user) { - assertNull(user); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_linkWithPopup_multipleUsers_success() { - // Test link with popup on multiple users. - asyncTestCase.waitForSignals(6); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertObjectEquals(provider, actualProvider); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertObjectEquals(provider, actualProvider); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - startPopupTimeoutCalls++; - // Both users already ready, handle events for both. - if (startPopupTimeoutCalls == 2) { - recordedHandler(expectedAuthEvent1); - recordedHandler(expectedAuthEvent2); - // User one can only handle first event. - assertTrue(user1.canHandleAuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, '1234')); - assertFalse(user1.canHandleAuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, '5678')); - // User two can only handle second event. - assertTrue(user2.canHandleAuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, '5678')); - assertFalse(user2.canHandleAuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, '1234')); - } - return new goog.Promise(function(resolve, reject) {}); - }).$times(2); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // The expected successful link via popup Auth event for first user. - var expectedAuthEvent1 = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - // The expected successful link via popup Auth event for second user. - var expectedAuthEvent2 = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - '5678', - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // Number of popup timeout calls. - var startPopupTimeoutCalls = 0; - fireauth.AuthEventManager.ENABLED = true; - var firstCall = true; - // Generate event ID depending on user calling it. - stubs.replace( - fireauth.util, - 'generateEventId', - function(prefix) { - // Skip other calls for generateEventId. - // This is used for testing that popup and redirects are supported. - if (!prefix) { - return 'random'; - } - if (firstCall) { - firstCall = false; - return '1234'; - } else { - return '5678'; - } - }); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // Finish popup and redirect link should be called for each user. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - if (this == user1) { - // Resolve with first expected result for first user. - return goog.Promise.resolve(expectedPopupResult1); - } else { - // Resolve with second expected result for second user. - return goog.Promise.resolve(expectedPopupResult2); - } - }); - // Create 2 users, it doesn't matter if they have same parameters. - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - var user2 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // The expected popup results. - var expectedPopupResult1 = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - var expectedPopupResult2 = { - 'user': user2, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - var provider = new fireauth.GoogleAuthProvider(); - // Enable popup and redirect on the first user. - user1.enablePopupRedirect(); - // linkWithPopup should succeed with the first expected popup result. - user1.linkWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult1, popupResult); - asyncTestCase.signal(); - }); - var provider = new fireauth.GoogleAuthProvider(); - // Enable popup and redirect on the first user. - user2.enablePopupRedirect(); - // linkWithPopup should succeed with the second expected popup result. - user2.linkWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult2, popupResult); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_multipleUsers_success() { - // Test reauth with popup on multiple users. - asyncTestCase.waitForSignals(2); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertObjectEquals(provider, actualProvider); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertObjectEquals(provider, actualProvider); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - startPopupTimeoutCalls++; - if (startPopupTimeoutCalls == 2) { - recordedHandler(expectedAuthEvent1); - recordedHandler(expectedAuthEvent2); - // User one can only handle first event. - assertTrue(user1.canHandleAuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, '1234')); - assertFalse(user1.canHandleAuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, '5678')); - // User two can only handle second event. - assertTrue(user2.canHandleAuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, '5678')); - assertFalse(user2.canHandleAuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, '1234')); - } - return new goog.Promise(function(resolve, reject) {}); - }).$times(2); - mockControl.$replayAll(); - var recordedHandler = null; - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - }); - // The expected successful reauth via popup Auth event for first user. - var expectedAuthEvent1 = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - // The expected successful reauth via popup Auth event for second user. - var expectedAuthEvent2 = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - '5678', - 'http://www.example.com/#response', - 'SESSION_ID'); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // Number of popup timeout calls. - var startPopupTimeoutCalls = 0; - fireauth.AuthEventManager.ENABLED = true; - var firstCall = true; - // Generate event ID depending on user calling it. - stubs.replace( - fireauth.util, - 'generateEventId', - function(prefix) { - // Skip other calls for generateEventId. - // This is used for testing that popup and redirects are supported. - if (!prefix) { - return 'random'; - } - if (firstCall) { - firstCall = false; - return '1234'; - } else { - return '5678'; - } - }); - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Finish popup and redirect reauth should be called for each user. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - if (this == user1) { - // Resolve with first expected result for first user. - return goog.Promise.resolve(expectedPopupResult1); - } else { - // Resolve with second expected result for second user. - return goog.Promise.resolve(expectedPopupResult2); - } - }); - // Create 2 users, it doesn't matter if they have same parameters. - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - var user2 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // The expected popup results. - var expectedPopupResult1 = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - var expectedPopupResult2 = { - 'user': user2, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - var provider = new fireauth.GoogleAuthProvider(); - // Enable popup and redirect on the first user. - user1.enablePopupRedirect(); - // reauthenticateWithPopup should succeed with the first expected popup - // result. - user1.reauthenticateWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult1, popupResult); - asyncTestCase.signal(); - }); - var provider = new fireauth.GoogleAuthProvider(); - // Enable popup and redirect on the first user. - user2.enablePopupRedirect(); - // reauthenticateWithPopup should succeed with the second expected popup - // result. - user2.reauthenticateWithPopup(provider).then(function(popupResult) { - assertObjectEquals(expectedPopupResult2, popupResult); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_timeout() { - fireauth.AuthEventManager.ENABLED = true; - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - // Do nothing on first call to simulate timeout. - return new goog.Promise(function(resolve, reject) {}); - }).$once(); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return new goog.Promise(function(resolve, reject) {}); - }).$once(); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected expire error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.EXPIRED_POPUP_REQUEST); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }; - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // This will resolve only for the second link with popup operation. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - '5678', - 'http://www.example.com/#response', - 'SESSION_ID'); - // The expected event IDs for each operation. - var expectedEventId = ['1234', '5678']; - // expectedEventId current index. - var index = 0; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Called twice. - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // Popup may try to close on resolution if still open (called twice). - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - // Called twice. - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // A popup event ID should be generated. - stubs.replace( - fireauth.util, - 'generateEventId', - function(prefix) { - // Skip other calls for generateEventId. - // This is used for testing that popup and redirects are supported. - if (!prefix) { - return 'random'; - } - // Return the next available event ID. - return expectedEventId[index++]; - }); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - return goog.Promise.resolve(expectedPopupResult); - }); - asyncTestCase.waitForSignals(6); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Enable popup and redirect on user. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // Call link with popup first. This will be expired after the second call. - user1.linkWithPopup(provider).thenCatch(function(error) { - // The second call should force this call to expire. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // Call link with popup second. - user1.linkWithPopup(provider).then(function(popupResult) { - // This will cancel the previous popup operation and eventually resolve. - assertObjectEquals(expectedPopupResult, popupResult); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_timeout() { - fireauth.AuthEventManager.ENABLED = true; - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - // Do nothing on first call to simulate timeout. - return new goog.Promise(function(resolve, reject) {}); - }).$once(); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return new goog.Promise(function(resolve, reject) {}); - }).$once(); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected expire error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.EXPIRED_POPUP_REQUEST); - // The expected popup result. - var expectedPopupResult = { - 'user': user1, - 'credential': expectedGoogleCredential, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }; - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - // This will resolve only for the second reauth with popup operation. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - '5678', - 'http://www.example.com/#response', - 'SESSION_ID'); - // The expected event IDs for each operation. - var expectedEventId = ['1234', '5678']; - // expectedEventId current index. - var index = 0; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - // Called twice. - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - }); - // Popup may try to close on resolution if still open (called twice). - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - // Called twice. - assertEquals(expectedPopup, win); - }); - // A popup event ID should be generated. - stubs.replace( - fireauth.util, - 'generateEventId', - function(prefix) { - // Skip other calls for generateEventId. - // This is used for testing that popup and redirects are supported. - if (!prefix) { - return 'random'; - } - // Return the next available event ID. - return expectedEventId[index++]; - }); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - return goog.Promise.resolve(expectedPopupResult); - }); - asyncTestCase.waitForSignals(2); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Enable popup and redirect on user. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // Call reauth with popup first. This will be expired after the second call. - user1.reauthenticateWithPopup(provider).thenCatch(function(error) { - // The second call should force this call to expire. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // Call reauth with popup second. - user1.reauthenticateWithPopup(provider).then(function(popupResult) { - // This will cancel the previous popup operation and eventually resolve. - assertObjectEquals(expectedPopupResult, popupResult); - asyncTestCase.signal(); - }); -} - - -function testUser_linkWithPopup_error() { - asyncTestCase.waitForSignals(3); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.LINK_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return new goog.Promise(function(resolve, reject) {}); - }); - mockControl.$replayAll(); - // Set the backend user info with no linked providers. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected error reported in expected Auth event. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // The expected Auth event with the error. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_POPUP, - '1234', - null, - null, - expectedError); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - asyncTestCase.signal(); - return expectedPopup; - }); - // Popup may try to close due to error if still open. - stubs.replace( - fireauth.util, - 'closeWindow', - function(win) { - assertEquals(expectedPopup, win); - asyncTestCase.signal(); - }); - // A popup event ID should be generated. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Since the expected Auth event already has an error, this should not be - // called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - fail('Auth error should not trigger finishPopupAndRedirectLink!'); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // linkWithPopup should throw the expected error. - user1.linkWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUser_reauthenticateWithPopup_error() { - asyncTestCase.waitForSignals(1); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return new goog.Promise(function(resolve, reject) {}); - }); - mockControl.$replayAll(); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected error reported in expected Auth event. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // The expected Auth event with the error. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - '1234', - null, - null, - expectedError); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - fireauth.AuthEventManager.ENABLED = true; - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - goog.testing.recordFunction(function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - })); - // Popup may try to close due to error if still open. - stubs.replace( - fireauth.util, - 'closeWindow', - goog.testing.recordFunction(function(win) { - assertEquals(expectedPopup, win); - })); - // A popup event ID should be generated. - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - return expectedEventId; - }); - // Since the expected Auth event already has an error, this should not be - // called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - fail('Auth error should not trigger finishPopupAndRedirectReauth!'); - }); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Enable popup and redirect. - user1.enablePopupRedirect(); - var provider = new fireauth.GoogleAuthProvider(); - // reauthenticateWithPopup should throw the expected error. - user1.reauthenticateWithPopup(provider).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - - -function testUser_linkWithPopup_alreadyLinked() { - // User on server has the federated provider linked already. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponseGoogleProviderData); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - return goog.Promise.resolve(getAccountInfoResponse); - }); - // Don't actually open the popup. - stubs.replace(fireauth.util, 'popup', function(url, name) { - return {'close': function() {}}; - }); - - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.linkWithPopup(new fireauth.GoogleAuthProvider()) - .thenCatch(function(actualError) { - fireauth.common.testHelper.assertErrorEquals(new fireauth.AuthError( - fireauth.authenum.Error.PROVIDER_ALREADY_LINKED), actualError); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testUser_returnFromLinkWithRedirect_success_withoutPostBody() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // The expected credential. - var expectedCred = fireauth.GoogleAuthProvider.credential( - {'accessToken': 'ACCESS_TOKEN'}); - // The expected link via redirect Auth event for the current user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // Return the expected result. - var result = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }); - return goog.Promise.resolve(result); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(1); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID matching the dispatched one. - user1.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Get redirect result should return expected result with current user and - // the expected credential. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.LINK, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromLinkWithRedirect_success_withPostBody() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // The expected link via redirect Auth event for the current user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - '1234', - 'http://www.example.com/callback', - 'SESSION_ID', - null, - 'POST_BODY'); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - // Return the expected result. - var result = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': null, - 'additionalUserInfo': expectedSamlAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }); - return goog.Promise.resolve(result); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(1); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID matching the dispatched one. - user1.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Get redirect result should return expected result with current user and - // the expected credential. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, null, expectedSamlAdditionalUserInfo, - fireauth.constants.OperationType.LINK, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromLinkWithRedirect_success_tenantId() { - // Verify that if tenant ID is in the redirect Auth event, it will be passed - // to finishPopupAndRedirectLink handler and the redirect result with the - // tenant user will be returned. - fireauth.AuthEventManager.ENABLED = true; - var expectedTenantId = 'TENANT_ID'; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // The expected credential. - var expectedCred = fireauth.GoogleAuthProvider.credential( - {'accessToken': 'ACCESS_TOKEN'}); - // The expected link via redirect Auth event for the current user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - // Return the expected result. - var result = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }); - return goog.Promise.resolve(result); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(1); - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID matching the dispatched one. - tenantUser.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // Get redirect result should return expected result with current user and - // the expected credential. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - tenantUser, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.LINK, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromReauthenticateWithRedirect_success_noPostBody() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // The expected credential. - var expectedCred = fireauth.GoogleAuthProvider.credential( - {'accessToken': 'ACCESS_TOKEN'}); - // The expected reauth via redirect Auth event for the current user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // Return the expected result. - var result = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }); - return goog.Promise.resolve(result); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(1); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID matching the dispatched one. - user1.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Get redirect result should return expected result with current user and - // the expected credential. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromReauthenticateWithRedirect_success_postBody() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // The expected reauth via redirect Auth event for the current user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - '1234', - 'http://www.example.com/callback', - 'SESSION_ID', - null, - 'POST_BODY'); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/callback', requestUri); - assertEquals('SESSION_ID', sessionId); - assertEquals('POST_BODY', postBody); - assertNull(tenantId); - // Return the expected result. - var result = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': null, - 'additionalUserInfo': expectedSamlAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }); - return goog.Promise.resolve(result); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(1); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID matching the dispatched one. - user1.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Get redirect result should return expected result with current user and - // the expected credential. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, null, expectedSamlAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromReauthenticateWithRedirect_success_tenantId() { - // Verify that if tenant ID is in the redirect Auth event, it will be passed - // to finishPopupAndRedirectReauth handler and the redirect result with the - // tenant user will be returned. - fireauth.AuthEventManager.ENABLED = true; - var expectedTenantId = 'TENANT_ID'; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // The expected credential. - var expectedCred = fireauth.GoogleAuthProvider.credential( - {'accessToken': 'ACCESS_TOKEN'}); - // The expected reauth via redirect Auth event for the current user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID', - null, - null, - expectedTenantId); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertEquals(expectedTenantId, tenantId); - // Return the expected result. - var result = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }); - return goog.Promise.resolve(result); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(1); - accountInfo['tenantId'] = expectedTenantId; - var tenantUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID matching the dispatched one. - tenantUser.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - tenantUser.enablePopupRedirect(); - // Get redirect result should return expected result with current user and - // the expected credential. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - tenantUser, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromLinkWithRedirect_success_multipleUsers() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Both users should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - assertTrue(manager.isSubscribed(user1)); - assertTrue(manager.isSubscribed(user2)); - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()).$times(2); - mockControl.$replayAll(); - // The expected credential. - var expectedCred = fireauth.GoogleAuthProvider.credential( - {'accessToken': 'ACCESS_TOKEN'}); - // The expected link via redirect Auth event for the first user only. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - // Finish popup and redirect link should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - // User1 will handle this only. - assertEquals(user1, this); - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - assertNull(tenantId); - // Return the expected result. - var result = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.LINK - }); - asyncTestCase.signal(); - return goog.Promise.resolve(result); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(3); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID matching the dispatched one. - user1.setRedirectEventId('1234'); - var user2 = new fireauth.AuthUser(config, tokenResponse, accountInfo2); - user2.setRedirectEventId('5678'); - // Enable popup and redirect on both. - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - user1.enablePopupRedirect(); - user2.enablePopupRedirect(); - // Get redirect result should return expected result with first user and - // the expected credential. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.LINK, response); - asyncTestCase.signal(); - }); - // This should also resolve to the same result. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - user1, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.LINK, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromReauthenticateWithRedirect_success_multipleUsers() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Both users should be subscribed. - var manager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - assertTrue(manager.isSubscribed(user1)); - assertTrue(manager.isSubscribed(user2)); - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()).$times(2); - mockControl.$replayAll(); - // The expected credential. - var expectedCred = fireauth.GoogleAuthProvider.credential( - {'accessToken': 'ACCESS_TOKEN'}); - // The expected reauth via redirect Auth event for the first user only. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - // Finish popup and redirect reauth should be called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - goog.testing.recordFunction( - function(requestUri, sessionId, tenantId, postBody) { - // User1 will handle this only. - assertEquals(user1, this); - assertEquals('http://www.example.com/#response', requestUri); - assertEquals('SESSION_ID', sessionId); - assertNull(postBody); - // Return the expected result. - var result = fireauth.object.makeReadonlyCopy({ - 'user': this, - 'credential': expectedCred, - 'additionalUserInfo': expectedAdditionalUserInfo, - 'operationType': fireauth.constants.OperationType.REAUTHENTICATE - }); - return goog.Promise.resolve(result); - })); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(1); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID matching the dispatched one. - user1.setRedirectEventId('1234'); - var user2 = new fireauth.AuthUser(config, tokenResponse, accountInfo2); - user2.setRedirectEventId('5678'); - // Enable popup and redirect on both. - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - user1.enablePopupRedirect(); - user2.enablePopupRedirect(); - // Get redirect result should return expected result with first user and - // the expected credential. - authEventManager.getRedirectResult().then(function(response) { - assertEquals( - 1, - fireauth.AuthUser.prototype.finishPopupAndRedirectReauth - .getCallCount()); - fireauth.common.testHelper.assertUserCredentialResponse( - user1, expectedCred, expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromLinkWithRedirect_invalidUser() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // Successful link via redirect for a different user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - 'OTHER_EVENT_ID', - 'http://www.example.com/#response', - 'SESSION_ID'); - // Since the expected Auth event already has an error, this should not be - // called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectLink should not call due to UID mismatch!'); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - asyncTestCase.waitForSignals(1); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID that does not match the event's. - user1.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Get redirect result should resolve to null as the user does not match the - // redirect Auth event's. Keep in mind if the Auth event's user exists in - // the current window, this should return that user in the response. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, null, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromReauthenticateWithRedirect_invalidUser() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // Successful reauth via redirect for a different user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - 'OTHER_EVENT_ID', - 'http://www.example.com/#response', - 'SESSION_ID'); - // Since the expected Auth event already has an error, this should not be - // called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectReauth should not call due to UID ' + - 'mismatch!'); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - asyncTestCase.waitForSignals(1); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID that does not match the event's. - user1.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Get redirect result should resolve to null as the user does not match the - // redirect Auth event's. Keep in mind if the Auth event's user exists in - // the current window, this should return that user in the response. - authEventManager.getRedirectResult().then(function(response) { - fireauth.common.testHelper.assertUserCredentialResponse( - null, null, null, null, response); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromLinkWithRedirect_error() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // The link with redirect expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // The expected Auth event with the redirect error. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, - '1234', - null, - null, - expectedError); - // Since the expected Auth event already has an error, this should not be - // called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectLink', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectLink should not call due to event error!'); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(2); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID that matches Auth event's. - user1.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Get redirect result should throw the expected error. - var manager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - manager.getRedirectResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_returnFromReauthenticateWithRedirect_error() { - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - asyncTestCase.signal(); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - mockControl.$replayAll(); - // The reauth with redirect expected error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // The expected Auth event with the redirect error. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - '1234', - null, - null, - expectedError); - // Since the expected Auth event already has an error, this should not be - // called. - stubs.replace( - fireauth.AuthUser.prototype, - 'finishPopupAndRedirectReauth', - function(requestUri, sessionId, tenantId, postBody) { - fail('finishPopupAndRedirectReauth should not call due to event ' + - 'error!'); - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - asyncTestCase.waitForSignals(1); - var user1 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Assume pending redirect event ID that matches Auth event's. - user1.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user1.enablePopupRedirect(); - // Get redirect result should throw the expected error. - var manager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - manager.getRedirectResult().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - }); -} - - -/** - * Helper function to simulate session invalidation for a specific user public - * operation. - * @param {string} fn The user specific function name to test. - * @param {!Array<*>} args The array of arguments to pass to apply on the user - * function. - * @param {!fireauth.AuthError} invalidationError The specific invalidation - * error to simulate. - */ -function simulateSessionInvalidation(fn, args, invalidationError) { - asyncTestCase.waitForSignals(1); - // Event trackers. - var stateChangeCounter = 0; - var authChangeCounter = 0; - var userInvalidateCounter = 0; - var userDeletedCounter = 0; - accountInfo['uid'] = '679'; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // Track token change. - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - authChangeCounter++; - }); - // Track user invalidation. - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidateCounter++; - }); - // Track user deletion. - goog.events.listen( - user, fireauth.UserEventType.USER_DELETED, function(event) { - userDeletedCounter++; - }); - // State change should be triggered. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - // Simulate invalidation error via getIdToken. - // This error could be triggered via other RPC when old token is still cached, - // but should behave the same as calls are chained to getIdToken and in this - // case, it is easier to test with getIdToken error as all APIs call that - // before calling other backend APIs. - stubs.replace( - fireauth.StsTokenManager.prototype, - 'getToken', - function(opt_forceRefresh) { - return goog.Promise.reject(invalidationError); - }); - // Apply the user function with the provided arguments. - user[fn].apply(user, args).thenCatch(function(error) { - // Only user invalidation event should be triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(0, userDeletedCounter); - assertEquals(1, userInvalidateCounter); - // Expected error should be thrown. - fireauth.common.testHelper.assertErrorEquals(invalidationError, error); - // Retry. The cached error should be thrown. - user[fn].apply(user, args).thenCatch(function(error) { - // Should be cached and no new events triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(0, userDeletedCounter); - assertEquals(1, userInvalidateCounter); - // Expected error should be thrown. - fireauth.common.testHelper.assertErrorEquals(invalidationError, error); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_sessionInvalidation_reload_tokenExpired() { - // Test user invalidation with token expired error on user.reload. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation('reload', [], invalidationError); -} - - -function testUser_sessionInvalidation_reload_userDisabled() { - // Test user invalidation with user disabled error on user.reload. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation('reload', [], invalidationError); -} - - -function testUser_sessionInvalidation_getIdToken_tokenExpired() { - // Test user invalidation with token expired error on user.getIdToken. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation('getIdToken', [], invalidationError); -} - - -function testUser_sessionInvalidation_getIdToken_userDisabled() { - // Test user invalidation with user disabled error on user.getIdToken. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation('getIdToken', [], invalidationError); -} - - -function testUser_sessionInvalidation_link_tokenExpired() { - // Test user invalidation with token expired error on user.link. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'link', - [ - fireauth.GoogleAuthProvider.credential(null, 'googleAccessToken') - ], - invalidationError); -} - - -function testUser_sessionInvalidation_link_tokenExpired() { - // Test user invalidation with token expired error on user.linkWithCredential. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'linkWithCredential', - [ - fireauth.GoogleAuthProvider.credential(null, 'googleAccessToken') - ], - invalidationError); -} - - -function testUser_sessionInvalidation_link_userDisabled() { - // Test user invalidation with user disabled error on user.link. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'link', - [ - fireauth.GoogleAuthProvider.credential(null, 'googleAccessToken') - ], - invalidationError); -} - - -function testUser_sessionInvalidation_link_userDisabled() { - // Test user invalidation with user disabled error on user.linkWithCredential. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'linkWithCredential', - [ - fireauth.GoogleAuthProvider.credential(null, 'googleAccessToken') - ], - invalidationError); -} - - -function testUser_sessInvalid_linkAndRetrieveDataWithCredential_tokenExpired() { - // Test user invalidation with token expired error on - // user.linkAndRetrieveDataWithCredential. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'linkAndRetrieveDataWithCredential', - [ - fireauth.GoogleAuthProvider.credential(null, 'googleAccessToken') - ], - invalidationError); -} - - -function testUser_sessInvalid_linkAndRetrieveDataWithCredential_userDisabled() { - // Test user invalidation with user disabled error on - // user.linkAndRetrieveDataWithCredential. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'linkAndRetrieveDataWithCredential', - [ - fireauth.GoogleAuthProvider.credential(null, 'googleAccessToken') - ], - invalidationError); -} - - -function testUser_sessionInvalidation_updateEmail_tokenExpired() { - // Test user invalidation with token expired error on user.updateEmail. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'updateEmail', ['user@example.com'], invalidationError); -} - - -function testUser_sessionInvalidation_updateEmail_userDisabled() { - // Test user invalidation with user disabled error on user.updateEmail. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'updateEmail', ['user@example.com'], invalidationError); -} - - -function testUser_sessionInvalidation_updatePassword_tokenExpired() { - // Test user invalidation with token expired error on user.updatePassword. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'updatePassword', ['password'], invalidationError); -} - - -function testUser_sessionInvalidation_updatePassword_userDisabled() { - // Test user invalidation with user disabled error on user.updatePassword. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'updatePassword', ['password'], invalidationError); -} - - -function testUser_sessionInvalidation_updateProfile_tokenExpired() { - // Test user invalidation with token expired error on user.updateProfile. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'updateProfile', - [ - { - displayName: 'John Doe' - } - ], - invalidationError); -} - - -function testUser_sessionInvalidation_updateProfile_userDisabled() { - // Test user invalidation with user disabled error on user.updateProfile. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'updateProfile', - [ - { - displayName: 'John Doe' - } - ], - invalidationError); -} - - -function testUser_sessionInvalidation_unlink_tokenExpired() { - // Test user invalidation with token expired error on user.unlink. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation('unlink', ['password'], invalidationError); -} - - -function testUser_sessionInvalidation_unlink_userDisabled() { - // Test user invalidation with user disabled error on user.unlink. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation('unlink', ['password'], invalidationError); -} - - -function testUser_sessionInvalidation_delete_tokenExpired() { - // Test user invalidation with token expired error on user.delete. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation('delete', [], invalidationError); -} - - -function testUser_sessionInvalidation_delete_userDisabled() { - // Test user invalidation with user disabled error on user.delete. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation('delete', [], invalidationError); -} - - -function testUser_sessionInvalidation_linkWithPopup_tokenExpired() { - // Test user invalidation with token expired error on user.linkWithPopup. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - var popupCalls = 0; - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - popupCalls++; - if (popupCalls > 1) { - fail('The second call to linkWithPopup should fail without openin' + - 'g the popup!'); - } - return expectedPopup; - }); - simulateSessionInvalidation( - 'linkWithPopup', [new fireauth.GoogleAuthProvider()], invalidationError); -} - - -function testUser_sessionInvalidation_linkWithPopup_userDisabled() { - // Test user invalidation with user disabled error on user.linkWithPopup. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - function(url, name, width, height) { - return expectedPopup; - }); - simulateSessionInvalidation( - 'linkWithPopup', [new fireauth.GoogleAuthProvider()], invalidationError); -} - - -function testUser_sessionInvalidation_linkWithRedirect_tokenExpired() { - // Test user invalidation with token expired error on user.linkWithRedirect. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'linkWithRedirect', - [new fireauth.GoogleAuthProvider()], - invalidationError); -} - - -function testUser_sessionInvalidation_linkWithRedirect_userDisabled() { - // Test user invalidation with user disabled error on user.linkWithRedirect. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'linkWithRedirect', - [new fireauth.GoogleAuthProvider()], - invalidationError); -} - - -function testUser_sessionInvalidation_sendEmailVerification_tokenExpired() { - // Test user invalidation with token expired error on - // user.sendEmailVerification. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'sendEmailVerification', [], invalidationError); -} - - -function testUser_sessionInvalidation_sendEmailVerification_userDisabled() { - // Test user invalidation with user disabled error on - // user.sendEmailVerification. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'sendEmailVerification', [], invalidationError); -} - - -function testUser_sessionInvalidation_linkWithPhoneNumber_tokenExpired() { - // Test user invalidation with token expired error on - // user.linkWithPhoneNumber. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - simulateSessionInvalidation( - 'linkWithPhoneNumber', - [expectedPhoneNumber, appVerifier], - invalidationError); -} - - -function testUser_sessionInvalidation_linkWithPhoneNumber_userDisabled() { - // Test user invalidation with user disabled error on - // user.linkWithPhoneNumber. - var invalidationError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - simulateSessionInvalidation( - 'linkWithPhoneNumber', - [expectedPhoneNumber, appVerifier], - invalidationError); -} - - -function testUser_sessionInvalidation_otherRpc() { - // Confirm if session invalidation thrown in other RPC, it is caught and - // cached. - // Expected session invalidation error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - // Event trackers. - var stateChangeCounter = 0; - var authChangeCounter = 0; - var userInvalidateCounter = 0; - var userDeletedCounter = 0; - // Track RPC call. - rpcTriggered = 0; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - // Track token change. - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - authChangeCounter++; - }); - // Track user invalidation. - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidateCounter++; - }); - // Track user deletion. - goog.events.listen( - user, fireauth.UserEventType.USER_DELETED, function(event) { - userDeletedCounter++; - }); - // State change should be triggered. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - // Simulate error in this RPC. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - rpcTriggered++; - return goog.Promise.reject(expectedError); - }); - asyncTestCase.waitForSignals(1); - // This should throw expected error. No event should trigger. - user.reload().thenCatch(function(error) { - // RPC called. - assertEquals(1, rpcTriggered); - // Only user invalidation event should be triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(0, userDeletedCounter); - assertEquals(1, userInvalidateCounter); - // Expected error should be thrown. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // This will succeed with only state change triggering. - user.reload().thenCatch(function() { - // Should be cached and no new events triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(0, userDeletedCounter); - assertEquals(1, userInvalidateCounter); - // RPC should not be called again. - assertEquals(1, rpcTriggered); - // Expected error should be thrown. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_reload_nonSessionInvalidationErrors() { - // Confirm non session invalidation errors are ignored and no error caching - // happens in that case. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Whether to trigger the error. - var triggerError = true; - // Track state changed - var stateChangeCounter = 0; - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - // Listen to state changes. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - // No other events should be triggered. - assertNoTokenEvents(user); - assertNoUserInvalidatedEvents(user); - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAccountInfoByIdToken', - function(idToken) { - // Throw error initially. - if (triggerError) { - return goog.Promise.reject(expectedError); - } - // Resolve on next call. - return goog.Promise.resolve(getAccountInfoResponse); - }); - asyncTestCase.waitForSignals(1); - // This should throw expected error. No event should trigger. - user.reload().thenCatch(function(error) { - assertEquals(0, stateChangeCounter); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Do not trigger error on next call. - triggerError = false; - // This will succeed with only state change triggering. - user.reload().then(function() { - assertEquals(1, stateChangeCounter); - asyncTestCase.signal(); - }); - }); -} - - -function testUser_proactiveRefresh_startAndStop() { - // Test proactive token refresh called with expected configurations. - // Record getIdToken calls. - stubs.replace( - fireauth.AuthUser.prototype, - 'getIdToken', - goog.testing.recordFunction()); - var proactiveRefreshInstance = mockControl.createStrictMock( - fireauth.ProactiveRefresh); - var proactiveRefreshConstructor = mockControl.createConstructorMock( - fireauth, 'ProactiveRefresh'); - // Listen to proactive refresh initialization and confirm arguments passed. - proactiveRefreshConstructor( - ignoreArgument, - ignoreArgument, - ignoreArgument, - fireauth.TokenRefreshTime.RETRIAL_MIN_WAIT, - fireauth.TokenRefreshTime.RETRIAL_MAX_WAIT, - false).$does( - function(operation, retryPolicy, getWaitDuration, lowerBound, - upperBound, runsInBackground) { - // Confirm operation forces refresh of token. - assertEquals( - 0, fireauth.AuthUser.prototype.getIdToken.getCallCount()); - // Run operation. - operation(); - // getIdToken(true) should be called underneath. - assertEquals( - 1, fireauth.AuthUser.prototype.getIdToken.getCallCount()); - assertTrue( - fireauth.AuthUser.prototype.getIdToken.getLastCall() - .getArgument(0)); - // Confirm retry policy only returns true for network errors. - assertTrue(retryPolicy(new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED))); - // Do not retry for all other common errors. - assertFalse(retryPolicy(new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR))); - assertFalse(retryPolicy(new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED))); - assertFalse(retryPolicy(new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED))); - // Confirm getWaitDuration returns expected value. - assertEquals( - 4800 * 1000 - fireauth.TokenRefreshTime.OFFSET_DURATION, - getWaitDuration()); - return proactiveRefreshInstance; - }).$once(); - // Confirm proactive refresh start and stop called. - proactiveRefreshInstance.isRunning().$returns(false); - proactiveRefreshInstance.start().$once(); - proactiveRefreshInstance.stop().$once(); - mockControl.$replayAll(); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - user.startProactiveRefresh(); - user.stopProactiveRefresh(); -} - - -function testUser_proactiveRefresh_externalTokenRefresh() { - // Test proactive token refresh is reset on each external token refresh call. - var proactiveRefreshInstance = mockControl.createStrictMock( - fireauth.ProactiveRefresh); - var proactiveRefreshConstructor = mockControl.createConstructorMock( - fireauth, 'ProactiveRefresh'); - proactiveRefreshConstructor( - ignoreArgument, - ignoreArgument, - ignoreArgument, - fireauth.TokenRefreshTime.RETRIAL_MIN_WAIT, - fireauth.TokenRefreshTime.RETRIAL_MAX_WAIT, - false).$returns(proactiveRefreshInstance); - proactiveRefreshInstance.isRunning().$returns(false); - proactiveRefreshInstance.start(); - // Token change event. - proactiveRefreshInstance.isRunning().$returns(true); - proactiveRefreshInstance.stop(); - proactiveRefreshInstance.start(); - // Second token change event. - proactiveRefreshInstance.isRunning().$returns(true); - proactiveRefreshInstance.stop(); - proactiveRefreshInstance.start(); - mockControl.$replayAll(); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - user.startProactiveRefresh(); - // Force an external token refresh and confirm the proactive refresh is reset. - user.dispatchEvent( - fireauth.UserEventType.TOKEN_CHANGED); - // Force another token change event. - user.dispatchEvent( - fireauth.UserEventType.TOKEN_CHANGED); -} - - -function testUser_proactiveRefresh_destroy() { - // Test proactive token refresh stopped on user destruction. - var proactiveRefreshInstance = mockControl.createStrictMock( - fireauth.ProactiveRefresh); - var proactiveRefreshConstructor = mockControl.createConstructorMock( - fireauth, 'ProactiveRefresh'); - proactiveRefreshConstructor( - ignoreArgument, - ignoreArgument, - ignoreArgument, - fireauth.TokenRefreshTime.RETRIAL_MIN_WAIT, - fireauth.TokenRefreshTime.RETRIAL_MAX_WAIT, - false).$returns(proactiveRefreshInstance); - proactiveRefreshInstance.isRunning().$returns(false); - proactiveRefreshInstance.start().$once(); - // Should be called on user.destroy(). - proactiveRefreshInstance.stop().$once(); - mockControl.$replayAll(); - - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); - user.startProactiveRefresh(); - // This should stop proactive refresh. - user.destroy(); -} - - -function testLinkWithPhoneNumber_success() { - app = firebase.initializeApp(config1, config1['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - var expectedVerificationId = 'VERIFICATION_ID'; - var expectedCode = '123456'; - var expectedCredential = fireauth.PhoneAuthProvider.credential( - expectedVerificationId, expectedCode); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - // Expected promise to be returned by linkAndRetrieveDataWithCredential. - var expectedPromise = new goog.Promise(function(resolve, reject) {}); - // Phone Auth provider instance. - var phoneAuthProviderInstance = - mockControl.createStrictMock(fireauth.PhoneAuthProvider); - // Phone Auth provider constructor mock. - var phoneAuthProviderConstructor = mockControl.createConstructorMock( - fireauth, 'PhoneAuthProvider'); - getAccountInfoByIdToken(tokenResponse['idToken']).$returns( - goog.Promise.resolve(getAccountInfoResponse)).$once(); - // Provider instance should be initialized with the expected Auth instance - // and return the expected phone Auth provider instance. - phoneAuthProviderConstructor(auth) - .$returns(phoneAuthProviderInstance).$once(); - // verifyPhoneNumber called on provider instance with the expected phone - // number and appVerifier. This would resolve with the expected verification - // ID. - phoneAuthProviderInstance.verifyPhoneNumber( - expectedPhoneNumber, appVerifier) - .$returns(goog.Promise.resolve(expectedVerificationId)).$once(); - // Code confirmation should call linkWithCredential with the - // expected credential. - stubs.replace( - fireauth.AuthUser.prototype, - 'linkWithCredential', - goog.testing.recordFunction(function(cred) { - // Confirm expected credential passed. - assertObjectEquals( - expectedCredential.toPlainObject(), - cred.toPlainObject()); - // Return expected promise. - return expectedPromise; - })); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.linkWithPhoneNumber(expectedPhoneNumber, appVerifier) - .then(function(confirmationResult) { - // Confirmation result returned should contain expected verification ID. - assertEquals( - expectedVerificationId, confirmationResult['verificationId']); - // Code confirmation should return the same response as the underlying - // linkWithCredential. - assertEquals(expectedPromise, confirmationResult.confirm(expectedCode)); - // Confirm linkWithCredential called once. - assertEquals( - 1, - fireauth.AuthUser.prototype.linkWithCredential - .getCallCount()); - // Confirm linkWithCredential is bound to current user. - assertEquals( - user, - fireauth.AuthUser.prototype.linkWithCredential - .getLastCall().getThis()); - asyncTestCase.signal(); - }); -} - - -function testLinkWithPhoneNumber_error_noAuthInstance() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'No firebase.auth.Auth instance is available for the Firebase App ' + - '\'' + config1['appName'] + '\'!'); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(tokenResponse['idToken']).$returns( - goog.Promise.resolve(getAccountInfoResponse)).$once(); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // This should fail since no corresponding Auth instance is found. - user.linkWithPhoneNumber(expectedPhoneNumber, appVerifier) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testLinkWithPhoneNumber_error_alreadyLinked() { - app = firebase.initializeApp(config1, config1['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.PROVIDER_ALREADY_LINKED); - // Add a phone Auth provider to the current user to trigger the provider - // already linked error. - getAccountInfoResponse['users'][0]['providerUserInfo'] - .push(getAccountInfoResponsePhoneAuthProviderData); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(tokenResponse['idToken']).$returns( - goog.Promise.resolve(getAccountInfoResponse)).$once(); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // No provider data linked yet. - assertEquals(0, user.providerData.length); - // This should fail since there is already a phone number provider on the - // current user. - user.linkWithPhoneNumber(expectedPhoneNumber, appVerifier) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testReauthenticateWithPhoneNumber_success() { - app = firebase.initializeApp(config1, config1['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - var expectedVerificationId = 'VERIFICATION_ID'; - var expectedCode = '123456'; - var expectedCredential = fireauth.PhoneAuthProvider.credential( - expectedVerificationId, expectedCode); - // Expected promise to be returned by reauthenticateWithCredential. - var expectedPromise = new goog.Promise(function(resolve, reject) {}); - // Phone Auth provider instance. - var phoneAuthProviderInstance = - mockControl.createStrictMock(fireauth.PhoneAuthProvider); - // Phone Auth provider constructor mock. - var phoneAuthProviderConstructor = mockControl.createConstructorMock( - fireauth, 'PhoneAuthProvider'); - // Provider instance should be initialized with the expected Auth instance - // and return the expected phone Auth provider instance. - phoneAuthProviderConstructor(auth) - .$returns(phoneAuthProviderInstance).$once(); - // verifyPhoneNumber called on provider instance with the expected phone - // number and appVerifier. This would resolve with the expected verification - // ID. - phoneAuthProviderInstance.verifyPhoneNumber( - expectedPhoneNumber, appVerifier) - .$returns(goog.Promise.resolve(expectedVerificationId)).$once(); - // Code confirmation should call reauthenticateWithCredential - // with the expected credential. - stubs.replace( - fireauth.AuthUser.prototype, - 'reauthenticateWithCredential', - goog.testing.recordFunction(function(cred) { - // Confirm expected credential passed. - assertObjectEquals( - expectedCredential.toPlainObject(), - cred.toPlainObject()); - // Return expected promise. - return expectedPromise; - })); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.reauthenticateWithPhoneNumber(expectedPhoneNumber, appVerifier) - .then(function(confirmationResult) { - // Confirmation result returned should contain expected verification ID. - assertEquals( - expectedVerificationId, confirmationResult['verificationId']); - // Code confirmation should return the same response as the underlying - // reauthenticateWithCredential. - assertEquals(expectedPromise, confirmationResult.confirm(expectedCode)); - // Confirm reauthenticateWithCredential called once. - assertEquals( - 1, - fireauth.AuthUser.prototype - .reauthenticateWithCredential.getCallCount()); - // Confirm reauthenticateWithCredential is bound to current user. - assertEquals( - user, - fireauth.AuthUser.prototype - .reauthenticateWithCredential - .getLastCall() - .getThis()); - asyncTestCase.signal(); - }); -} - - -function testReauthenticateWithPhoneNumber_success_skipInvalidation() { - // Test that reauthenticateWithPhoneNumber will be allowed to run after token - // expiration. - app = firebase.initializeApp(config1, config1['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - // Expected token expired error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - var expectedVerificationId = 'VERIFICATION_ID'; - var expectedCode = '123456'; - var expectedCredential = fireauth.PhoneAuthProvider.credential( - expectedVerificationId, expectedCode); - // Expected promise to be returned by reauthenticateWithCredential. - var expectedPromise = new goog.Promise(function(resolve, reject) {}); - // Mock StsTokenManager.prototype.getToken. - var getToken = mockControl.createMethodMock( - fireauth.StsTokenManager.prototype, 'getToken'); - // Phone Auth provider instance. - var phoneAuthProviderInstance = - mockControl.createStrictMock(fireauth.PhoneAuthProvider); - // Phone Auth provider constructor mock. - var phoneAuthProviderConstructor = mockControl.createConstructorMock( - fireauth, 'PhoneAuthProvider'); - // Initial call to getToken should return token expired. - getToken(true).$does(function(opt_forceRefresh) { - return goog.Promise.reject(expectedError); - }).$once(); - // Provider instance should be initialized with the expected Auth instance - // and return the expected phone Auth provider instance. - phoneAuthProviderConstructor(auth) - .$returns(phoneAuthProviderInstance).$once(); - // verifyPhoneNumber called on provider instance with the expected phone - // number and appVerifier. This would resolve with the expected verification - // ID. - phoneAuthProviderInstance.verifyPhoneNumber( - expectedPhoneNumber, appVerifier) - .$returns(goog.Promise.resolve(expectedVerificationId)).$once(); - // Code confirmation should call reauthenticateWithCredential - // with the expected credential. - stubs.replace( - fireauth.AuthUser.prototype, - 'reauthenticateWithCredential', - goog.testing.recordFunction(function(cred) { - // Confirm expected credential passed. - assertObjectEquals( - expectedCredential.toPlainObject(), - cred.toPlainObject()); - // Return expected promise. - return expectedPromise; - })); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - user.getIdToken(true).thenCatch(function(error) { - // Expected token expired error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // This should be allowed to run even though the token is expired. - // All other non reauth operation will fail immediately and throw the cached - // error. - return user.reauthenticateWithPhoneNumber(expectedPhoneNumber, appVerifier); - }).then(function(confirmationResult) { - // Confirmation result returned should contain expected verification ID. - assertEquals( - expectedVerificationId, confirmationResult['verificationId']); - // Code confirmation should return the same response as the underlying - // reauthenticateWithCredential. - assertEquals(expectedPromise, confirmationResult.confirm(expectedCode)); - // Confirm reauthenticateWithCredential called once. - assertEquals( - 1, - fireauth.AuthUser.prototype.reauthenticateWithCredential - .getCallCount()); - // Confirm reauthenticateWithCredential is bound to current - // user. - assertEquals( - user, - fireauth.AuthUser.prototype.reauthenticateWithCredential - .getLastCall().getThis()); - asyncTestCase.signal(); - }); -} - - -function testReauthenticateWithPhoneNumber_error_noAuthInstance() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'No firebase.auth.Auth instance is available for the Firebase App ' + - '\'' + config1['appName'] + '\'!'); - - asyncTestCase.waitForSignals(1); - var user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - // This should fail since no corresponding Auth instance is found. - user.reauthenticateWithPhoneNumber(expectedPhoneNumber, appVerifier) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testUser_customLocaleChanges() { - // Listen to all custom locale header calls on RpcHandler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateCustomLocaleHeader', - goog.testing.recordFunction()); - // Dummy event dispatchers. - var dispatcher1 = createEventDispatcher(); - var dispatcher2 = createEventDispatcher(); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - - // Set language to German. - user.setLanguageCode('de'); - // User language should be updated. - assertEquals('de', user.getLanguageCode()); - // Rpc handler language should be updated. - assertEquals( - 1, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertEquals( - 'de', - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); - - // Set language to null. - user.setLanguageCode(null); - assertEquals( - 2, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertNull( - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); - assertNull(user.getLanguageCode()); - - // Set dispatcher1 as language code dispatcher. - user.setLanguageCodeChangeDispatcher(dispatcher1); - dispatcher1.dispatchEvent(new fireauth.Auth.LanguageCodeChangeEvent('fr')); - dispatcher2.dispatchEvent(new fireauth.Auth.LanguageCodeChangeEvent('ru')); - // Only first dispatcher should be detected. - assertEquals( - 3, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertEquals( - 'fr', - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); - assertEquals('fr', user.getLanguageCode()); - - // Set dispatcher2 as language code dispatcher. - user.setLanguageCodeChangeDispatcher(dispatcher2); - dispatcher1.dispatchEvent(new fireauth.Auth.LanguageCodeChangeEvent('fr')); - dispatcher2.dispatchEvent(new fireauth.Auth.LanguageCodeChangeEvent('ru')); - // Only second dispatcher should be detected. - assertEquals( - 4, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - assertEquals( - 'ru', - fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getLastCall() - .getArgument(0)); - assertEquals('ru', user.getLanguageCode()); - - // Remove all dispatchers. - user.setLanguageCodeChangeDispatcher(null); - dispatcher1.dispatchEvent(new fireauth.Auth.LanguageCodeChangeEvent('fr')); - dispatcher2.dispatchEvent(new fireauth.Auth.LanguageCodeChangeEvent('ru')); - // No additional events detected. - assertEquals( - 4, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); - // Last set language remains. - assertEquals('ru', user.getLanguageCode()); - - // Set dispatcher2 as language code change dispatcher and destroy the user. - user.setLanguageCodeChangeDispatcher(dispatcher2); - user.destroy(); - dispatcher2.dispatchEvent(new fireauth.Auth.LanguageCodeChangeEvent('ar')); - // No additional events detected. - assertEquals( - 4, fireauth.RpcHandler.prototype.updateCustomLocaleHeader.getCallCount()); -} - - -function testUser_emulatorConfigChanges() { - // Listen to all custom emulator config change calls on RpcHandler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmulatorConfig', - goog.testing.recordFunction()); - // Dummy event dispatchers. - var dispatcher1 = createEventDispatcher(); - var dispatcher2 = createEventDispatcher(); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - var emulatorConfig = { - url: 'http://emulator.test.domain:1234', - }; - - var otherEmulatorConfig = { - url: 'http://other.emulator.host:9876' - }; - - // Set emulator config. - user.setEmulatorConfig(emulatorConfig); - // Rpc handler emulator config should be updated. - assertEquals( - 1, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount() - ); - assertObjectEquals( - emulatorConfig, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0) - ); - - // Set dispatcher1 as emulator config change dispatcher. - user.setEmulatorConfigChangeDispatcher(dispatcher1); - dispatcher1.dispatchEvent( - new fireauth.Auth.EmulatorConfigChangeEvent(emulatorConfig)); - dispatcher2.dispatchEvent( - new fireauth.Auth.EmulatorConfigChangeEvent(otherEmulatorConfig)); - // Only first dispatcher should be detected. - assertEquals( - 2, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount() - ); - assertObjectEquals( - emulatorConfig, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0) - ); -} - - -function testUser_frameworkLoggingChanges() { - // Helper function to get the client version for the test. - var getVersion = function(frameworks) { - return fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION, - frameworks); - }; - // Pipe through all framework IDs. - stubs.replace( - fireauth.util, - 'getFrameworkIds', - function(providedFrameworks) { - return providedFrameworks; - }); - // Listen to all client version header update calls on RpcHandler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateClientVersion', - goog.testing.recordFunction()); - // Dummy event dispatchers. - var dispatcher1 = createEventDispatcher(); - var dispatcher2 = createEventDispatcher(); - user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); - - // Set framework to version1. - user.setFramework(['v1']); - // Framework version should be updated. - assertArrayEquals(['v1'], user.getFramework()); - // Rpc handler language should be updated. - assertEquals( - 1, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); - assertEquals( - getVersion(['v1']), - fireauth.RpcHandler.prototype.updateClientVersion.getLastCall() - .getArgument(0)); - - // Set framework to empty array. - user.setFramework([]); - assertEquals( - 2, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); - assertEquals( - getVersion([]), - fireauth.RpcHandler.prototype.updateClientVersion.getLastCall() - .getArgument(0)); - assertArrayEquals([], user.getFramework()); - - // Set dispatcher1 as framework change dispatcher. - user.setFrameworkChangeDispatcher(dispatcher1); - dispatcher1.dispatchEvent( - new fireauth.Auth.FrameworkChangeEvent(['v1', 'v2'])); - dispatcher2.dispatchEvent( - new fireauth.Auth.FrameworkChangeEvent(['v3', 'v4'])); - // Only first dispatcher should be detected. - assertEquals( - 3, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); - assertEquals( - getVersion(['v1', 'v2']), - fireauth.RpcHandler.prototype.updateClientVersion.getLastCall() - .getArgument(0)); - assertArrayEquals(['v1', 'v2'], user.getFramework()); - - // Set dispatcher2 as framework change dispatcher. - user.setFrameworkChangeDispatcher(dispatcher2); - dispatcher1.dispatchEvent( - new fireauth.Auth.FrameworkChangeEvent(['v1', 'v2'])); - dispatcher2.dispatchEvent( - new fireauth.Auth.FrameworkChangeEvent(['v3', 'v4'])); - // Only second dispatcher should be detected. - assertEquals( - 4, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); - assertEquals( - getVersion(['v3', 'v4']), - fireauth.RpcHandler.prototype.updateClientVersion.getLastCall() - .getArgument(0)); - assertArrayEquals(['v3', 'v4'], user.getFramework()); - - // Remove all dispatchers. - user.setFrameworkChangeDispatcher(null); - dispatcher1.dispatchEvent( - new fireauth.Auth.FrameworkChangeEvent(['v1', 'v2'])); - dispatcher2.dispatchEvent( - new fireauth.Auth.FrameworkChangeEvent(['v3', 'v4'])); - // No additional events detected. - assertEquals( - 4, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); - // Last framework list remains. - assertArrayEquals(['v3', 'v4'], user.getFramework()); - - // Set dispatcher2 as framework change dispatcher and destroy the user. - user.setFrameworkChangeDispatcher(dispatcher2); - user.destroy(); - dispatcher2.dispatchEvent( - new fireauth.Auth.FrameworkChangeEvent(['v1', 'v2'])); - // No additional events detected. - assertEquals( - 4, fireauth.RpcHandler.prototype.updateClientVersion.getCallCount()); -} - - -function testUserMetadata() { - // Test initialization. - var userMetadata1 = new fireauth.UserMetadata(createdAt, lastLoginAt); - assertEquals( - fireauth.util.utcTimestampToDateString(lastLoginAt), - userMetadata1['lastSignInTime']); - assertEquals( - fireauth.util.utcTimestampToDateString(createdAt), - userMetadata1['creationTime']); - // Confirm read-only. - userMetadata1['lastSignInTime'] = 'bla'; - userMetadata1['creationTime'] = 'bla'; - assertEquals( - fireauth.util.utcTimestampToDateString(lastLoginAt), - userMetadata1['lastSignInTime']); - assertEquals( - fireauth.util.utcTimestampToDateString(createdAt), - userMetadata1['creationTime']); - - var userMetadata2 = new fireauth.UserMetadata(createdAt); - assertEquals( - fireauth.util.utcTimestampToDateString(createdAt), - userMetadata2['creationTime']); - assertNull(userMetadata2['lastSignInTime']); - - var userMetadata3 = new fireauth.UserMetadata(); - assertNull(userMetadata3['creationTime']); - assertNull(userMetadata3['lastSignInTime']); - - // Test cloning. - assertObjectEquals(userMetadata1, userMetadata1.clone()); - assertObjectEquals(userMetadata2, userMetadata2.clone()); - assertObjectEquals(userMetadata3, userMetadata3.clone()); - - // Test toPlainObject. - assertObjectEquals( - { - 'lastLoginAt': lastLoginAt, - 'createdAt': createdAt - }, - userMetadata1.toPlainObject()); - assertObjectEquals( - { - 'lastLoginAt': null, - 'createdAt': createdAt - }, - userMetadata2.toPlainObject()); - assertObjectEquals( - { - 'lastLoginAt': null, - 'createdAt': null - }, - userMetadata3.toPlainObject()); -} - - -/** - * Tests when a 2-factor user reauthenticates with a first factor credential - * and then recovers with a second factor assertion. - * @return {!goog.Promise} A promise that resolves when the test - * completes. - */ -function testReauthenticateWithCredential_multiFactor_success() { - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - app = firebase.initializeApp(config, config['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - // Second factor requirement error. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var stateChangeCounter = 0; - var tokenChangeCounter = 0; - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - var mockCredential = mockControl.createStrictMock( - fireauth.AuthCredential); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - // Simulate first factor re-auth triggers second factor requirement. - mockCredential.matchIdTokenWithUid(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, uid) { - assertObjectEquals(user.getRpcHandler(), rpcHandler); - assertEquals(user['uid'], uid); - return goog.Promise.reject(serverResponseError); - }); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(user.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }); - // User reload will return the enrolled factors. - getAccountInfoByIdToken(multiFactorTokenResponse.idToken) - .$once() - .$returns(goog.Promise.resolve(multiFactorGetAccountInfoResponse)); - mockControl.$replayAll(); - - // Simulate user who upgraded to a second factor on a different device and is - // re-authenticated in the current stale session. - var user = new fireauth.AuthUser( - config1, nonMultiFactorTokenResponse, accountInfo); - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - tokenChangeCounter++; - }); - assertNoUserInvalidatedEvents(user); - asyncTestCase.waitForSignals(1); - - return user.reauthenticateAndRetrieveDataWithCredential(mockCredential) - .then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth, error.resolver.auth); - assertEquals(0, tokenChangeCounter); - assertEquals(0, stateChangeCounter); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, - result); - - // Enrolled factors updated. - assertArrayEquals( - [ - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[0]), - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[1]) - ], - user.multiFactor.enrolledFactors); - assertEquals('defaultUserId', user['uid']); - // Token and state change triggered. - assertEquals(1, stateChangeCounter); - assertEquals(1, tokenChangeCounter); - return user.getIdToken(); - }) - .then(function(idToken) { - // Confirm tokens updated. - assertEquals(multiFactorTokenResponse.idToken, idToken); - assertEquals('MULTI_FACTOR_REFRESH_TOKEN', user.refreshToken); - asyncTestCase.signal(); - }); -} - - -/** - * Tests when a 2-factor user reauthenticates with a first factor credential - * and then fails to recover with a second factor assertion. - * @return {!goog.Promise} A promise that resolves when the test - * completes. - */ -function testReauthenticateWithCredential_multiFactor_assertionError() { - // Expected assertion processing error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CODE_EXPIRED); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - app = firebase.initializeApp(config, config['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - // Second factor requirement error. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var stateChangeCounter = 0; - var tokenChangeCounter = 0; - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - var mockCredential = mockControl.createStrictMock( - fireauth.AuthCredential); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - // Simulate first factor re-auth triggers second factor requirement. - mockCredential.matchIdTokenWithUid(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, uid) { - assertObjectEquals(user.getRpcHandler(), rpcHandler); - assertEquals(user['uid'], uid); - return goog.Promise.reject(serverResponseError); - }); - // Second factor assertion processing fails. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(user.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.reject(expectedError); - }); - mockControl.$replayAll(); - - // Simulate user who upgraded to a second factor on a different device and is - // re-authenticated in the current stale session. - var user = new fireauth.AuthUser( - config1, nonMultiFactorTokenResponse, accountInfo); - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - tokenChangeCounter++; - }); - assertNoUserInvalidatedEvents(user); - asyncTestCase.waitForSignals(1); - - return user.reauthenticateAndRetrieveDataWithCredential(mockCredential) - .then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth, error.resolver.auth); - assertEquals(0, tokenChangeCounter); - assertEquals(0, stateChangeCounter); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(fail, function(error) { - // Assertion error should be caught. - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - // Token and state change should not be triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, tokenChangeCounter); - asyncTestCase.signal(); - }); -} - - -/** - * Tests when a 2-factor user with an expired token re-authenticates. - * @return {!goog.Promise} A promise that resolves when the test - * completes. - */ -function testUser_getIdTokenResult_expiredToken_reauth_multiFactor() { - // Test when token is expired and user is reauthenticated. - // User should be validated after, even though user invalidation event is - // triggered. - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - app = firebase.initializeApp(config, config['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - multiFactorTokenResponse['accessToken'] = multiFactorTokenResponse.idToken; - // Second factor requirement error. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // Expected token expired error. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - // Event trackers. - var stateChangeCounter = 0; - var authChangeCounter = 0; - var userInvalidateCounter = 0; - var mockCredential = mockControl.createStrictMock( - fireauth.AuthCredential); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - var getToken = mockControl.createMethodMock( - fireauth.StsTokenManager.prototype, 'getToken'); - getToken(true).$once().$does(function() { - return goog.Promise.reject(expectedError); - }); - // Simulate first factor re-auth triggers second factor requirement. - mockCredential.matchIdTokenWithUid(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, uid) { - assertObjectEquals(user.getRpcHandler(), rpcHandler); - assertEquals(user['uid'], uid); - return goog.Promise.reject(serverResponseError); - }); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(user.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }); - getToken(undefined).$once().$does(function() { - return goog.Promise.resolve(multiFactorTokenResponse); - }); - // User reload will return the enrolled factors. - getAccountInfoByIdToken(multiFactorTokenResponse.idToken) - .$once() - .$returns(goog.Promise.resolve(multiFactorGetAccountInfoResponse)); - getToken(undefined).$once().$does(function() { - return goog.Promise.resolve(multiFactorTokenResponse); - }); - mockControl.$replayAll(); - - user = new fireauth.AuthUser( - config, nonMultiFactorTokenResponse, accountInfo); - // Track token changes. - goog.events.listen( - user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - authChangeCounter++; - }); - // Track user invalidation events. - goog.events.listen( - user, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidateCounter++; - }); - // State change should be triggered. - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - asyncTestCase.waitForSignals(1); - - // Call getIdToken, it should trigger the expected error. - return user.getIdToken(true) - .then(fail, function(error) { - // Refresh token nullified. - assertEquals( - nonMultiFactorTokenResponse['refreshToken'], user['refreshToken']); - // Confirm expected error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No state change. - assertEquals(0, stateChangeCounter); - // No Auth change. - assertEquals(0, authChangeCounter); - // User invalidated change. - assertEquals(1, userInvalidateCounter); - // Call again, it should not trigger any other event. - return user.getIdToken(); - }).then(fail, function(error) { - // Resolves with same error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // No additional change. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(1, userInvalidateCounter); - // Assume user reauthenticated. - return user.reauthenticateAndRetrieveDataWithCredential(mockCredential); - }).then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth, error.resolver.auth); - // No additional event triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, authChangeCounter); - assertEquals(1, userInvalidateCounter); - return error.resolver.resolveSignIn(mockAssertion); - }).then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, - result); - - // Enrolled factors updated. - assertArrayEquals( - [ - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[0]), - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[1]) - ], - user.multiFactor.enrolledFactors); - - // Set via reauthentication. - assertEquals( - multiFactorTokenResponse['refreshToken'], user['refreshToken']); - // Auth token change triggered. - assertEquals(1, authChangeCounter); - // State change triggers, after reauthentication. - assertEquals(1, stateChangeCounter); - // Shouldn't trigger again. - assertEquals(1, userInvalidateCounter); - // This should return cached token set via reauthentication. - return user.getIdToken(); - }).then(function(idToken) { - // Shouldn't trigger again. - assertEquals(1, authChangeCounter); - assertEquals(1, stateChangeCounter); - assertEquals(1, userInvalidateCounter); - // Refresh token should be updated along with ID token. - assertEquals( - multiFactorTokenResponse['idToken'], idToken); - assertEquals( - multiFactorTokenResponse['refreshToken'], user['refreshToken']); - asyncTestCase.signal(); - }); -} - - -/** - * Tests when a 2-factor user re-authenticates and the second factor assertion - * returns tokens for a different user. - * @return {!goog.Promise} A promise that resolves when the test - * completes. - */ -function testReauthenticateWithCredential_multiFactor_mismatchError() { - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - app = firebase.initializeApp(config, config['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - // Expected user mismatch error at the end of the reauthentication operation. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH); - // Second factor requirement error. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var stateChangeCounter = 0; - var tokenChangeCounter = 0; - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - var mockCredential = mockControl.createStrictMock( - fireauth.AuthCredential); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - // Simulate first factor re-auth triggers second factor requirement. - mockCredential.matchIdTokenWithUid(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, uid) { - assertObjectEquals(user.getRpcHandler(), rpcHandler); - assertEquals(user['uid'], uid); - return goog.Promise.reject(serverResponseError); - }); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(user.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }); - mockControl.$replayAll(); - - // In this case, the original user re-authenticates with a credential that - // belongs to a different multi-factor account. - var accountInfoWithDifferentUid = goog.object.clone(accountInfo); - accountInfoWithDifferentUid['uid'] = 'MISMATCHED_UID'; - var user = new fireauth.AuthUser( - config, nonMultiFactorTokenResponse, accountInfoWithDifferentUid); - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - tokenChangeCounter++; - }); - assertNoUserInvalidatedEvents(user); - asyncTestCase.waitForSignals(1); - - return user.reauthenticateAndRetrieveDataWithCredential(mockCredential) - .then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth, error.resolver.auth); - assertEquals(0, tokenChangeCounter); - assertEquals(0, stateChangeCounter); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(fail, function(error) { - // User mismatch error should be triggered on sign-in completion. - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - // Token and state change should not be triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, tokenChangeCounter); - asyncTestCase.signal(); - }); -} - - -/** - * Tests when a 2-factor user reauthenticates with a popup and then recovers - * with a second factor assertion. - * @return {!goog.Promise} A promise that resolves when the test - * completes. - */ -function testReauthenticateWithPopup_multiFactor_success() { - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - goog.testing.recordFunction(function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - })); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - goog.testing.recordFunction(function(win) { - assertEquals(expectedPopup, win); - })); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - var config = { - 'apiKey': 'apiKey1', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - app = firebase.initializeApp(config, config['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - fireauth.AuthEventManager.ENABLED = true; - // Second factor requirement error. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var stateChangeCounter = 0; - var tokenChangeCounter = 0; - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Add Google as linked provider to confirm that reauth does not fail like - // linking does when called with an already linked provider. - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'google.com', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - var verifyAssertionForExisting = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'verifyAssertionForExisting'); - // Simulate first factor re-auth triggers second factor requirement. - verifyAssertionForExisting({ - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }).$does(function(request) { - return goog.Promise.reject(serverResponseError); - }); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$does(function(rpcHandler, session) { - assertObjectEquals(user.getRpcHandler(), rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }) - .$once(); - // User reload will return the enrolled factors. - getAccountInfoByIdToken(multiFactorTokenResponse.idToken) - .$once() - .$returns(goog.Promise.resolve(multiFactorGetAccountInfoResponse)); - mockControl.$replayAll(); - - // Simulate user who upgraded to a second factor on a different device and is - // re-authenticated in the current stale session. - var user = new fireauth.AuthUser( - config, nonMultiFactorTokenResponse, accountInfo); - user.addProviderData(providerData1); - // Enable popup and redirect. - user.enablePopupRedirect(); - - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - tokenChangeCounter++; - }); - assertNoUserInvalidatedEvents(user); - asyncTestCase.waitForSignals(1); - - var provider = new fireauth.GoogleAuthProvider(); - return user.reauthenticateWithPopup(provider) - .then(fail, function(error) { - // Confirm popup and closeWindow called in the process. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.popup.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.closeWindow.getCallCount()); - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth, error.resolver.auth); - assertEquals(0, tokenChangeCounter); - assertEquals(0, stateChangeCounter); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, - result); - - // Enrolled factors updated. - assertArrayEquals( - [ - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[0]), - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[1]) - ], - user.multiFactor.enrolledFactors); - assertEquals('defaultUserId', user['uid']); - // Token and state change triggered. - assertEquals(1, stateChangeCounter); - assertEquals(1, tokenChangeCounter); - return user.getIdToken(); - }) - .then(function(idToken) { - // Confirm tokens updated. - assertEquals(multiFactorTokenResponse.idToken, idToken); - assertEquals('MULTI_FACTOR_REFRESH_TOKEN', user.refreshToken); - asyncTestCase.signal(); - }); -} - - -/** - * Tests when a 2-factor user re-authenticates with a popup and the second - * factor assertion returns tokens for a different user. - * @return {!goog.Promise} A promise that resolves when the test - * completes. - */ -function testReauthenticateWithPopup_multiFactor_mismatchError() { - // Replace random number generator. - stubs.replace( - fireauth.util, - 'generateRandomString', - function() { - return '87654321'; - }); - // Simulate popup. - stubs.replace( - fireauth.util, - 'popup', - goog.testing.recordFunction(function(url, name, width, height) { - assertNull(url); - assertEquals('87654321', name); - assertEquals(fireauth.idp.Settings.GOOGLE.popupWidth, width); - assertEquals(fireauth.idp.Settings.GOOGLE.popupHeight, height); - return expectedPopup; - })); - // On success if popup is still opened, it will be closed. - stubs.replace( - fireauth.util, - 'closeWindow', - goog.testing.recordFunction(function(win) { - assertEquals(expectedPopup, win); - })); - stubs.replace( - fireauth.util, - 'generateEventId', - function() { - // A popup event ID should be generated. - return expectedEventId; - }); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - app = firebase.initializeApp(config, config['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - // Expected user mismatch error at the end of the reauthentication operation. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH); - fireauth.AuthEventManager.ENABLED = true; - // Second factor requirement error. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var stateChangeCounter = 0; - var tokenChangeCounter = 0; - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // The expected popup window object. - var expectedPopup = { - 'close': function() {} - }; - // The expected popup event ID. - var expectedEventId = '1234'; - // The expected successful reauth via popup Auth event. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, - expectedEventId, - 'http://www.example.com/#response', - 'SESSION_ID'); - // Add Google as linked provider to confirm that reauth does not fail like - // linking does when called with an already linked provider. - providerData1 = new fireauth.AuthUserInfo( - 'providerUserId1', - 'google.com', - 'user1@example.com', - null, - 'https://www.example.com/user1/photo.png'); - var recordedHandler = null; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.processPopup( - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument, - ignoreArgument).$does(function( - actualPopupWin, - actualMode, - actualProvider, - actualOnInit, - actualOnError, - actualEventId, - actualAlreadyRedirected) { - assertEquals(expectedPopup, actualPopupWin); - assertEquals(fireauth.AuthEvent.Type.REAUTH_VIA_POPUP, actualMode); - assertEquals(provider, actualProvider); - assertEquals(expectedEventId, actualEventId); - assertFalse(actualAlreadyRedirected); - actualOnInit(); - return goog.Promise.resolve(); - }); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - recordedHandler = handler; - }); - oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); - oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); - oAuthSignInHandlerInstance.startPopupTimeout( - ignoreArgument, ignoreArgument, ignoreArgument) - .$does(function(popupWin, onError, delay) { - recordedHandler(expectedAuthEvent); - return goog.Promise.resolve(); - }); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var verifyAssertionForExisting = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'verifyAssertionForExisting'); - // Simulate first factor re-auth triggers second factor requirement. - verifyAssertionForExisting({ - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }).$does(function(request) { - return goog.Promise.reject(serverResponseError); - }); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$once() - .$does(function(rpcHandler, session) { - assertObjectEquals(user.getRpcHandler(),rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }); - mockControl.$replayAll(); - - // In this case, the original user re-authenticates with a credential that - // belongs to a different multi-factor account. - var accountInfoWithDifferentUid = goog.object.clone(accountInfo); - accountInfoWithDifferentUid['uid'] = 'MISMATCHED_UID'; - var user = new fireauth.AuthUser( - config, nonMultiFactorTokenResponse, accountInfoWithDifferentUid); - user.addProviderData(providerData1); - // Enable popup and redirect. - user.enablePopupRedirect(); - - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - tokenChangeCounter++; - }); - assertNoUserInvalidatedEvents(user); - asyncTestCase.waitForSignals(1); - - var provider = new fireauth.GoogleAuthProvider(); - return user.reauthenticateWithPopup(provider) - .then(fail, function(error) { - // Confirm popup and closeWindow called in the process. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.popup.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.closeWindow.getCallCount()); - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth, error.resolver.auth); - assertEquals(0, tokenChangeCounter); - assertEquals(0, stateChangeCounter); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(fail, function(error) { - // User mismatch error should be triggered on sign-in completion. - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - // Token and state change should not be triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, tokenChangeCounter); - asyncTestCase.signal(); - }); -} - - -/** - * Tests when a 2-factor user returns from a reauthenticate with redirect - * operation and then recovers with a second factor assertion. - * @return {!goog.Promise} A promise that resolves when the test - * completes. - */ -function testReturnFromReauthenticateWithRedirect_multiFactor_success() { - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - app = firebase.initializeApp(config, config['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - var stateChangeCounter = 0; - var tokenChangeCounter = 0; - // Second factor requirement error. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // The expected reauth via redirect Auth event for the current user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - var verifyAssertionForExisting = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'verifyAssertionForExisting'); - // Simulate first factor re-auth triggers second factor requirement. - verifyAssertionForExisting({ - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }).$does(function(request) { - return goog.Promise.reject(serverResponseError); - }); - // Second factor assertion processing succeeds with updated tokens. - mockAssertion.process(ignoreArgument, ignoreArgument) - .$does(function(rpcHandler, session) { - assertObjectEquals(user.getRpcHandler(), rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }) - .$once(); - // User reload will return the enrolled factors. - getAccountInfoByIdToken(multiFactorTokenResponse.idToken) - .$once() - .$returns(goog.Promise.resolve(multiFactorGetAccountInfoResponse)); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - - // Simulate user who upgraded to a second factor on a different device and is - // re-authenticated in the current stale session. - var user = new fireauth.AuthUser( - config, nonMultiFactorTokenResponse, accountInfo); - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - tokenChangeCounter++; - }); - assertNoUserInvalidatedEvents(user); - // Assume pending redirect event ID matching the dispatched one. - user.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - - return pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user.enablePopupRedirect(); - // Get redirect result should reject with multi-factor error. - return authEventManager.getRedirectResult() - .then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth, error.resolver.auth); - assertEquals(0, tokenChangeCounter); - assertEquals(0, stateChangeCounter); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(function(result) { - // Expected result returned. - fireauth.common.testHelper.assertUserCredentialResponse( - // Expected current user returned. - user, - // Expected credential returned. - expectedGoogleCredential, - // Expected additional user info. - expectedAdditionalUserInfo, - fireauth.constants.OperationType.REAUTHENTICATE, - result); - - // Enrolled factors updated. - assertArrayEquals( - [ - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[0]), - fireauth.MultiFactorInfo.fromServerResponse( - multiFactorGetAccountInfoResponse['users'][0].mfaInfo[1]) - ], - user.multiFactor.enrolledFactors); - assertEquals('defaultUserId', user['uid']); - // Token and state change triggered. - assertEquals(1, stateChangeCounter); - assertEquals(1, tokenChangeCounter); - return user.getIdToken(); - }) - .then(function(idToken) { - // Confirm tokens updated. - assertEquals(multiFactorTokenResponse.idToken, idToken); - assertEquals('MULTI_FACTOR_REFRESH_TOKEN', user.refreshToken); - asyncTestCase.signal(); - }); - }); -} - - -/** - * Tests when a 2-factor user returns from a reauthenticate with redirect - * operation and the second factor assertion returns tokens for a different - * user. - * @return {!goog.Promise} A promise that resolves when the test - * completes. - */ -function testReturnFromReauthenticateWithRedirect_multiFactor_mismatchError() { - // Expected user mismatch error at the end of the reauthentication operation. - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH); - var config = { - 'apiKey': 'API_KEY', - 'authDomain': 'subdomain.firebaseapp.com', - 'appName': 'appId1' - }; - app = firebase.initializeApp(config, config['appName']); - auth = new fireauth.Auth(app); - // Stub Auth on the App instance above. - stubs.set(app, 'auth', function() { - return auth; - }); - var stateChangeCounter = 0; - var tokenChangeCounter = 0; - // Second factor requirement error. - var serverResponseError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - multiFactorErrorServerResponse); - var expectedSession = new fireauth.MultiFactorSession( - null, - multiFactorErrorServerResponse.mfaPendingCredential); - // The expected reauth via redirect Auth event for the current user. - var expectedAuthEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.REAUTH_VIA_REDIRECT, - '1234', - 'http://www.example.com/#response', - 'SESSION_ID'); - fireauth.AuthEventManager.ENABLED = true; - // Mock OAuth sign in handler. - var oAuthSignInHandlerInstance = - mockControl.createStrictMock(fireauth.OAuthSignInHandler); - mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); - var instantiateOAuthSignInHandler = mockControl.createMethodMock( - fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); - instantiateOAuthSignInHandler( - ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, - ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); - oAuthSignInHandlerInstance.addAuthEventListener(ignoreArgument) - .$does(function(handler) { - // Dispatch expected Auth event immediately to simulate return from - // redirect operation. - handler(expectedAuthEvent); - }); - oAuthSignInHandlerInstance.initializeAndWait() - .$returns(goog.Promise.resolve()); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var verifyAssertionForExisting = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'verifyAssertionForExisting'); - // Simulate first factor re-auth triggers second factor requirement. - verifyAssertionForExisting({ - 'requestUri': 'http://www.example.com/#response', - 'sessionId': 'SESSION_ID', - 'postBody': null, - 'tenantId': null - }).$does(function(request) { - return goog.Promise.reject(serverResponseError); - }); - mockAssertion.process(ignoreArgument, ignoreArgument) - .$does(function(rpcHandler, session) { - assertObjectEquals(user.getRpcHandler(), rpcHandler); - assertObjectEquals(expectedSession, session); - return goog.Promise.resolve(multiFactorTokenResponse); - }) - .$once(); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - - // In this case, the original user re-authenticates with a credential that - // belongs to a different multi-factor account. - var accountInfoWithDifferentUid = goog.object.clone(accountInfo); - accountInfoWithDifferentUid['uid'] = 'MISMATCHED_UID'; - var user = new fireauth.AuthUser( - config, nonMultiFactorTokenResponse, accountInfoWithDifferentUid); - user.addStateChangeListener(function(userTemp) { - stateChangeCounter++; - return goog.Promise.resolve(); - }); - goog.events.listen(user, fireauth.UserEventType.TOKEN_CHANGED, - function(event) { - tokenChangeCounter++; - }); - assertNoUserInvalidatedEvents(user); - // Assume pending redirect event ID matching the dispatched one. - user.setRedirectEventId('1234'); - var pendingRedirectManager = new fireauth.storage.PendingRedirectManager( - config['apiKey'] + ':' + config['appName']); - var authEventManager = fireauth.AuthEventManager.getManager( - config['authDomain'], config['apiKey'], config['appName']); - - return pendingRedirectManager.setPendingStatus().then(function() { - // Enable popup and redirect. - user.enablePopupRedirect(); - // Get redirect result should reject with multi-factor error. - return authEventManager.getRedirectResult() - .then(fail, function(error) { - // Error should be intercepted and repackaged. - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals(auth, error.resolver.auth); - assertEquals(0, tokenChangeCounter); - assertEquals(0, stateChangeCounter); - return error.resolver.resolveSignIn(mockAssertion); - }) - .then(fail, function(error) { - // User mismatch error should be triggered on sign-in completion. - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - // Token and state change should not be triggered. - assertEquals(0, stateChangeCounter); - assertEquals(0, tokenChangeCounter); - asyncTestCase.signal(); - }); - }); -} diff --git a/packages/auth/test/cacherequest_test.js b/packages/auth/test/cacherequest_test.js deleted file mode 100644 index cd42e00aced..00000000000 --- a/packages/auth/test/cacherequest_test.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for cacherequest.js. - */ - -goog.provide('fireauth.CacheRequestTest'); - -goog.require('fireauth.CacheRequest'); -goog.require('goog.Promise'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.TestCase'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.CacheRequestTest'); - - -var cacheRequest; -var MyClass; -var instance; -var clock; - -function setUp() { - // Test class. - MyClass = function() { - this.counter = 0; - this.err = 0; - }; - // Method that always resolves with an incremented counter. - MyClass.prototype.func = function(par1, par2) { - assertEquals(1, par1); - assertEquals(2, par2); - this.counter++; - return goog.Promise.resolve(this.counter); - }; - // Method that always rejects with an incremented error message. - MyClass.prototype.func2 = function(par1, par2) { - assertEquals(1, par1); - assertEquals(2, par2); - this.err++; - return goog.Promise.reject(new Error(this.err)); - }; - instance = new MyClass(); - cacheRequest = new fireauth.CacheRequest(); -} - - -function tearDown() { - cacheRequest = null; -} - - -/** - * Test cache request when the cached request resolves successfully. - * @return {!goog.Promise} The result of the test. - */ -function cacheRequestWithoutErrors() { - clock = new goog.testing.MockClock(true); - cacheRequest.cache(instance.func, instance, [1, 2], 60 * 1000); - return cacheRequest.run().then(function(result) { - assertEquals(result, 1); - // This response should be returned from cache. - return cacheRequest.run(); - }).then(function(result) { - assertEquals(result, 1); - clock.tick(60 * 1000); - // This should bust the cache and send a request. - return cacheRequest.run(); - }).then(function(result) { - assertEquals(result, 2); - clock.tick(30 * 1000); - // This response should be returned from cache. - return cacheRequest.run(); - }).then(function(result) { - assertEquals(result, 2); - clock.tick(30 * 1000); - // This should bust the cache and send a request. - return cacheRequest.run(); - }).then(function(result) { - assertEquals(result, 3); - cacheRequest.purge(); - // This should bust the cache and send a request. - return cacheRequest.run(); - }).then(function(result) { - assertEquals(result, 4); - goog.dispose(clock); - }); -} - - -/** - * Test cache request when the cached request rejects with an error and errors - * are not to be cached. - * @return {!goog.Promise} The result of the test. - */ -function cacheRequestWithErrorsNotCached() { - clock = new goog.testing.MockClock(true); - cacheRequest.cache(instance.func2, instance, [1, 2], 60 * 1000); - return cacheRequest.run().thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 1); - // This response should not be returned from cache. - return cacheRequest.run(); - }).thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 2); - cacheRequest.purge(); - // This response should not be returned from cache. - return cacheRequest.run(); - }).thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 3); - goog.dispose(clock); - }); -} - - -/** - * Test cache request when the cached request rejects with an error and errors - * are set to be cached. - * @return {!goog.Promise} The result of the test. - */ -function cacheRequestWithErrorsCached() { - clock = new goog.testing.MockClock(true); - cacheRequest.cache(instance.func2, instance, [1, 2], 60 * 1000, true); - return cacheRequest.run().thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 1); - // This response should be returned from cache. - return cacheRequest.run(); - }).thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 1); - clock.tick(60 * 1000); - // This should bust the cache and send a request. - return cacheRequest.run(); - }).thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 2); - clock.tick(30 * 1000); - // This response should be returned from cache. - return cacheRequest.run(); - }).thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 2); - clock.tick(30 * 1000); - // This should bust the cache and send a request. - return cacheRequest.run(); - }).thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 3); - cacheRequest.purge(); - // This should bust the cache and send a request. - return cacheRequest.run(); - }).thenCatch(function(error) { - assertEquals(parseInt(error.message, 10), 4); - goog.dispose(clock); - }); -} - - -/** - * Install the test to run and runs it. - * @param {string} id The test identifier. - * @param {function():!goog.Promise} func The test function to run. - * @return {!goog.Promise} The result of the test. - */ -function installAndRunTest(id, func) { - var testCase = new goog.testing.TestCase(); - testCase.addNewTest(id, func); - return testCase.runTestsReturningPromise().then(function(result) { - assertTrue(result.complete); - assertEquals(1, result.totalCount); - assertEquals(1, result.runCount); - assertEquals(1, result.successCount); - assertEquals(0, result.errors.length); - }); -} - - -function testNoAvailableConfiguration() { - try { - cacheRequest.run(); - fail('Missing cache configuration should throw an error!'); - } catch(e) { - assertEquals('No available configuration cached!', e.message); - } -} - - -function testCacheRequestWithoutErrors() { - return installAndRunTest( - 'cacheRequestWithoutErrors', cacheRequestWithoutErrors); -} - - -function testCacheRequestWithErrorsNotCached() { - return installAndRunTest( - 'cacheRequestWithErrorsNotCached', cacheRequestWithErrorsNotCached); -} - - -function testCacheRequestWithErrorsCached() { - return installAndRunTest( - 'cacheRequestWithErrorsCached', cacheRequestWithErrorsCached); -} diff --git a/packages/auth/test/confirmationresult_test.js b/packages/auth/test/confirmationresult_test.js deleted file mode 100644 index b3368f0b80c..00000000000 --- a/packages/auth/test/confirmationresult_test.js +++ /dev/null @@ -1,203 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for confirmationresult.js - */ - -goog.provide('fireauth.ConfirmationResultTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.ConfirmationResult'); -goog.require('fireauth.PhoneAuthCredential'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.authenum.Error'); -/** @suppress {extraRequire} Needed for firebase.app().auth() */ -goog.require('fireauth.exports'); -goog.require('goog.Promise'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.TestCase'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.ConfirmationResultTest'); - - -var mockControl; -var app; -var auth; - - -function setUp() { - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); -} - - -function tearDown() { - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } -} - - -/** - * Install the test to run and runs it. - * @param {string} id The test identifier. - * @param {function():!goog.Promise} func The test function to run. - * @return {!goog.Promise} The result of the test. - */ -function installAndRunTest(id, func) { - // Initialize App and Auth before running the test. - app = firebase.initializeApp({ - apiKey: 'API_KEY' - }, 'test'); - auth = app.auth(); - var testCase = new goog.testing.TestCase(); - testCase.addNewTest(id, func); - return testCase.runTestsReturningPromise().then(function(result) { - assertTrue(result.complete); - // Display error detected. - if (result.errors.length) { - fail(result.errors.join('\n')); - } - assertEquals(1, result.totalCount); - assertEquals(1, result.runCount); - assertEquals(1, result.successCount); - assertEquals(0, result.errors.length); - // Delete App and Auth instances before resolving. - auth.delete(); - return app.delete(); - }); -} - - -function testConfirmationResult() { - var expectedVerificationId = 'VERIFICATION_ID'; - var expectedCode = '123456'; - var expectedPromise = new goog.Promise(function(resolve, reject) {}); - var credentialInstance = - mockControl.createStrictMock(fireauth.PhoneAuthCredential); - var credential = - mockControl.createMethodMock(fireauth.PhoneAuthProvider, 'credential'); - var credentialResolver = mockControl.createFunctionMock('credentialResolver'); - // Phone Auth credential will be initialized first. - credential(expectedVerificationId, expectedCode) - .$returns(credentialInstance).$once(); - // Credential resolver will be caled with the initialized credential instance. - // Return expected pending promise. - credentialResolver(credentialInstance).$returns(expectedPromise).$once(); - mockControl.$replayAll(); - - // Initialize a confirmation result with the expected parameters. - var confirmationResult = new fireauth.ConfirmationResult( - expectedVerificationId, credentialResolver); - // Check verificationId property. - assertEquals(expectedVerificationId, confirmationResult['verificationId']); - // Confirm read-only. - confirmationResult['verificationId'] = 'not readonly'; - assertEquals(expectedVerificationId, confirmationResult['verificationId']); - // Confirm expected credential resolver promise returned on confirmation. - assertEquals(expectedPromise, confirmationResult.confirm(expectedCode)); -} - - -function testConfirmationResult_initialize_success() { - return installAndRunTest('confirmationResult_initialize_success', function() { - var expectedVerificationId = 'VERIFICATION_ID'; - var expectedPhoneNumber = '+16505550101'; - var expectedRecaptchaToken = 'RECAPTCHA_TOKEN'; - var appVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(expectedRecaptchaToken); - } - }; - var phoneAuthProviderInstance = - mockControl.createStrictMock(fireauth.PhoneAuthProvider); - var phoneAuthProviderConstructor = mockControl.createConstructorMock( - fireauth, 'PhoneAuthProvider'); - var confirmationResultInstance = - mockControl.createStrictMock(fireauth.ConfirmationResult); - var confirmationResultConstructor = mockControl.createConstructorMock( - fireauth, 'ConfirmationResult'); - var credentialResolver = - mockControl.createFunctionMock('credentialResolver'); - // Provider instance should be initialized with the expected Auth instance. - phoneAuthProviderConstructor(auth) - .$returns(phoneAuthProviderInstance).$once(); - // verifyPhoneNumber called on provider instance with the expected phone - // number and appVerifier. This would resolve with the expected verification - // ID. - phoneAuthProviderInstance.verifyPhoneNumber( - expectedPhoneNumber, appVerifier) - .$returns(goog.Promise.resolve(expectedVerificationId)).$once(); - // ConfirmationResult instance should be initialized with the expected - // verification ID and the credential resolver. - confirmationResultConstructor(expectedVerificationId, credentialResolver) - .$returns(confirmationResultInstance).$once(); - mockControl.$replayAll(); - - // Initialize a confirmation result. - return fireauth.ConfirmationResult.initialize( - auth, expectedPhoneNumber, appVerifier, credentialResolver) - .then(function(result) { - // Expected confirmation result instance returned. - assertEquals(confirmationResultInstance, result); - }); - }); -} - - -function testConfirmationResult_initialize_error() { - return installAndRunTest('confirmationResult_initialize_error', function() { - var expectedPhoneNumber = '+16505550101'; - var expectedRecaptchaToken = 'RECAPTCHA_TOKEN'; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - var appVerifier = { - 'type': 'recaptcha', - 'verify': function() { - return goog.Promise.resolve(expectedRecaptchaToken); - } - }; - var phoneAuthProviderInstance = - mockControl.createStrictMock(fireauth.PhoneAuthProvider); - var phoneAuthProviderConstructor = mockControl.createConstructorMock( - fireauth, 'PhoneAuthProvider'); - var credentialResolver = - mockControl.createFunctionMock('credentialResolver'); - // Provider instance should be initialized with the expected Auth instance. - phoneAuthProviderConstructor(auth) - .$returns(phoneAuthProviderInstance).$once(); - // verifyPhoneNumber called on provider instance with the expected phone - // number and appVerifier. This would reject with the expected error. - phoneAuthProviderInstance.verifyPhoneNumber( - expectedPhoneNumber, appVerifier) - .$returns(goog.Promise.reject(expectedError)).$once(); - mockControl.$replayAll(); - - // Initialize a confirmation result. - return fireauth.ConfirmationResult.initialize( - auth, expectedPhoneNumber, appVerifier, credentialResolver) - .then(fail, function(error) { - // Expected error returned. - assertEquals(expectedError, error); - }); - }); -} diff --git a/packages/auth/test/cordovahandler_test.js b/packages/auth/test/cordovahandler_test.js deleted file mode 100644 index 67d98f5888d..00000000000 --- a/packages/auth/test/cordovahandler_test.js +++ /dev/null @@ -1,2950 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for cordovahandler.js. - */ - -goog.provide('fireauth.CordovaHandlerTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.CordovaHandler'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.UniversalLinkSubscriber'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.storage.AuthEventManager'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.OAuthHandlerManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Timer'); -goog.require('goog.crypt'); -goog.require('goog.crypt.Sha256'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.TestCase'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.CordovaHandlerTest'); - - -var stubs = new goog.testing.PropertyReplacer(); -var universalLinks; -var BuildInfo; -var cordova; -var universalLinkCb; - -var cordovaHandler; -var authDomain = 'subdomain.firebaseapp.com'; -var apiKey = 'apiKey1'; -var appName = 'appName1'; -var version = '3.0.0'; -var savePartialEventManager; -var storageKey; -var androidUA = 'Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Buil' + - 'd/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Sa' + - 'fari/534.30'; -var iOS8iPhoneUA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) A' + - 'ppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safar' + - 'i/600.1.4'; -var iOS9iPhoneUA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) A' + - 'ppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13C75 Safar' + - 'i/601.1'; -var mockLocalStorage; -var mockSessionStorage; - - -/** - * @param {string} str The string to hash. - * @return {string} The hashed string. - */ -function sha256(str) { - var sha256 = new goog.crypt.Sha256(); - sha256.update(str); - return goog.crypt.byteArrayToHex(sha256.digest()); -} - - -/** - * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be - * used as Internet Explorer adds the stack trace as a property of the object. - * @param {!fireauth.AuthError} expected - * @param {!fireauth.AuthError} actual - */ -function assertErrorEquals(expected, actual) { - assertObjectEquals(expected.toPlainObject(), actual.toPlainObject()); -} - - -/** - * Asserts that two Auth events are equivalent. - * @param {!fireauth.AuthEvent} expectedEvent - * @param {!fireauth.AuthEvent} actualEvent - */ -function assertAuthEventEquals(expectedEvent, actualEvent) { - assertObjectEquals( - expectedEvent.toPlainObject(), actualEvent.toPlainObject()); -} - - -/** - * Utility function to initialize the Cordova mock plugins. - * @param {?function(?string, function(!Object))} subscribe The universal link - * subscriber. - * @param {?string} packageName The package name. - * @param {?string} displayName The app display name. - * @param {boolean} isAvailable Whether browsertab is supported. - * @param {?function(string, ?function(), ?function())} openUrl The URL opener. - * @param {?function()} close The browsertab closer if applicable. - * @param {?function()} open The inappbrowser opener if available. - */ -function initializePlugins( - subscribe, packageName, displayName, isAvailable, openUrl, close, open) { - // Initializes all mock plugins. - universalLinks = { - subscribe: subscribe - }; - BuildInfo = { - packageName: packageName, - displayName: displayName - }; - cordova = { - plugins: { - browsertab: { - isAvailable: function(cb) { - cb(isAvailable); - }, - openUrl: openUrl, - close: close - } - }, - InAppBrowser: { - open: open - } - }; -} - - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); - // Initialize plugins. - initializePlugins( - function(eventName, cb) { - universalLinkCb = cb; - }, - 'com.example.app', - 'Test App', - true, - function(url, resolve, reject) {}, - goog.testing.recordFunction(), - goog.testing.recordFunction()); - stubs.replace( - fireauth.util, - 'checkIfCordova', - function() { - return goog.Promise.resolve(); - }); - // Storage key. - storageKey = 'apiKey1:appName1'; - // Storage manager helpers. - savePartialEventManager = new fireauth.storage.OAuthHandlerManager(); - getAndDeletePartialEventManager = - new fireauth.storage.AuthEventManager(storageKey); -} - - -function tearDown() { - // Clear storage. - window.localStorage.clear(); - window.sessionStorage.clear(); - // Reset stubs. - stubs.reset(); - // Clear plugins. - universalLinks = {}; - BuildInfo = {}; - cordova = {}; - cordovaHandler = null; - universalLinkCb = null; - if (goog.global['handleOpenURL']) { - delete goog.global['handleOpenURL']; - } - fireauth.UniversalLinkSubscriber.clear(); -} - - -/** - * Install the test to run and runs it. - * @param {string} id The test identifier. - * @param {function():!goog.Promise} func The test function to run. - * @return {!goog.Promise} The result of the test. - */ -function installAndRunTest(id, func) { - var testCase = new goog.testing.TestCase(); - testCase.addNewTest(id, func); - return testCase.runTestsReturningPromise().then(function(result) { - assertTrue(result.complete); - // Display error detected. - if (result.errors.length) { - fail(result.errors.join('\n')); - } - assertEquals(1, result.totalCount); - assertEquals(1, result.runCount); - assertEquals(1, result.successCount); - assertEquals(0, result.errors.length); - }); -} - - -function testCordovaHandler_initializeAndWait_success() { - return installAndRunTest('initializeAndWait_success', function() { - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - // Confirm should be initialized early. - assertTrue(cordovaHandler.shouldBeInitializedEarly()); - // Confirm has volatile sessionStorage. - assertTrue(cordovaHandler.hasVolatileStorage()); - // Confirm does not unload on redirect. - assertFalse(cordovaHandler.unloadsOnRedirect()); - // Should resolve successfully. - return cordovaHandler.initializeAndWait(); - }); -} - - -function testCordovaHandler_initializeAndWait_notCordovaError() { - return installAndRunTest('initializeAndWait_notCordovaError', function() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CORDOVA_NOT_READY); - // Simulate non Cordova environment. - stubs.replace( - fireauth.util, - 'checkIfCordova', - function() { - return goog.Promise.reject(expectedError); - }); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - return cordovaHandler.initializeAndWait().then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - assertErrorEquals(expectedError, error); - }); - }); -} - - -function testCordovaHandler_initializeAndWait_universalLinkError() { - return installAndRunTest('initializeAndWait_universalLinkError', function() { - // Universal links plugin not installed. - universalLinks = {}; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION, - 'cordova-universal-links-plugin-fix is not installed'); - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - var p = new goog.Promise(function(resolve, reject) { - cordovaHandler.addAuthEventListener(function(event) { - assertAuthEventEquals(noEvent, event); - resolve(); - }); - }); - return cordovaHandler.initializeAndWait().then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - assertErrorEquals(expectedError, error); - return p; - }); - }); -} - - -function testCordovaHandler_initializeAndWait_buildInfoError() { - return installAndRunTest('initializeAndWait_buildInfoError', function() { - // Buildinfo plugin not installed. - BuildInfo = {}; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION, - 'cordova-plugin-buildinfo is not installed'); - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - var p = new goog.Promise(function(resolve, reject) { - cordovaHandler.addAuthEventListener(function(event) { - assertAuthEventEquals(noEvent, event); - resolve(); - }); - }); - return cordovaHandler.initializeAndWait().then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - assertErrorEquals(expectedError, error); - return p; - }); - }); -} - - -function testCordovaHandler_initializeAndWait_browserTabError() { - return installAndRunTest('initializeAndWait_browserTabError', function() { - // browsertab plugin not installed. - cordova = {}; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION, - 'cordova-plugin-browsertab is not installed'); - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - var p = new goog.Promise(function(resolve, reject) { - cordovaHandler.addAuthEventListener(function(event) { - assertAuthEventEquals(noEvent, event); - resolve(); - }); - }); - return cordovaHandler.initializeAndWait().then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - assertErrorEquals(expectedError, error); - return p; - }); - }); -} - - -function testCordovaHandler_initializeAndWait_inAppBrowserError() { - return installAndRunTest('initializeAndWait_inAppBrowserError', function() { - // inappbrowser plugin not installed. - cordova.InAppBrowser = null; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION, - 'cordova-plugin-inappbrowser is not installed'); - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - var p = new goog.Promise(function(resolve, reject) { - cordovaHandler.addAuthEventListener(function(event) { - assertAuthEventEquals(noEvent, event); - resolve(); - }); - }); - return cordovaHandler.initializeAndWait().then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - assertErrorEquals(expectedError, error); - return p; - }); - }); -} - - -function testCordovaHandler_startPopupTimeout() { - return installAndRunTest('startPopupTimeout', function() { - // Should fail with operation not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - var onError = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - return cordovaHandler.startPopupTimeout({}, onError, 1000).then(function() { - assertEquals(1, onError.getCallCount()); - assertErrorEquals(expectedError, onError.getLastCall().getArgument(0)); - }); - }); -} - - -function testCordovaHandler_processPopup() { - return installAndRunTest('processPopup', function() { - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - var provider = new fireauth.GoogleAuthProvider(); - // Popup requests should fail with operation not supported error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - return cordovaHandler.processPopup( - {}, 'linkViaPopup', provider, onInit, onError, '1234', false) - .then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - assertErrorEquals(expectedError, error); - }); - }); -} - - -function testCordovaHandler_addRemoveAuthEventListeners() { - return installAndRunTest('addRemoveAuthEventListeners', function() { - // Wait for event to be handled. - var waitWhileInStorage = function() { - return goog.Timer.promise(10).then(function() { - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(authEvent) { - if (authEvent) { - return waitWhileInStorage(); - } - }); - }; - var partialEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - 'SESSION_ID', - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - 'SESSION_ID'); - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var handler1 = goog.testing.recordFunction(); - var handler2 = goog.testing.recordFunction(); - var handler3 = goog.testing.recordFunction(); - // Save some pending redirect that won't be handled and confirm it was - // cleared. - return savePartialEventManager.setAuthEvent(storageKey, partialEvent).then( - function() { - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler1); - cordovaHandler.addAuthEventListener(handler2); - cordovaHandler.addAuthEventListener(handler3); - return cordovaHandler.initializeAndWait(); - }).then(function() { - // Wait for event to be cleared. - return waitWhileInStorage(); - }).then(function() { - // All handlers should be called with no event. - assertEquals(1, handler1.getCallCount()); - assertAuthEventEquals(noEvent, handler1.getLastCall().getArgument(0)); - assertEquals(1, handler2.getCallCount()); - assertAuthEventEquals(noEvent, handler2.getLastCall().getArgument(0)); - assertEquals(1, handler3.getCallCount()); - assertAuthEventEquals(noEvent, handler3.getLastCall().getArgument(0)); - // Remove 2 handlers and trigger a new event. - cordovaHandler.removeAuthEventListener(handler1); - cordovaHandler.removeAuthEventListener(handler2); - // Simulate a redirect operation and the partial event is saved. - return savePartialEventManager.setAuthEvent(storageKey, partialEvent); - }).then(function() { - // Trigger the universal link with the OAuth response. - universalLinkCb({ - url: incomingUrl - }); - // Wait for event to be cleared from storage. - return waitWhileInStorage(); - }).then(function() { - // Only third handler called with completed event. - assertEquals(1, handler1.getCallCount()); - assertEquals(1, handler2.getCallCount()); - assertEquals(2, handler3.getCallCount()); - assertAuthEventEquals( - completeEvent, handler3.getLastCall().getArgument(0)); - }); - }); -} - - - -function testCordovaHandler_initialNoAuthEvent() { - return installAndRunTest('initialNoAuthEvent', function() { - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var handler; - var waitForHandler = new goog.Promise(function(resolve, reject) { - handler = resolve; - }); - // Use quick timeout to simulate no event triggered on initialization. - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.initializeAndWait().then(function() { - // Wait for handler to be called. - return waitForHandler; - }).then(function(authEvent) { - // No Auth event triggered. - assertAuthEventEquals(noEvent, authEvent); - }); - }); -} - - -function testCordovaHandler_initialNoAuthEvent_invalidLink() { - return installAndRunTest('initialNoAuthEvent_invalidLink', function() { - // This should have been previously saved in a process redirect call. - var partialEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - 'SESSION_ID', - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var incomingUrl = - 'http://example.firebaseapp.com/some/other/link'; - // The final resolved event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var handler; - var waitForHandler = new goog.Promise(function(resolve, reject) { - handler = resolve; - }); - // Trigger some non callback link. - universalLinks.subscribe = function(eventType, cb) { - cb({url: incomingUrl}); - }; - // Assume pending redirect event. - return savePartialEventManager.setAuthEvent(storageKey, partialEvent) - .then(function() { - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.initializeAndWait(); - }).then(function() { - // Wait for handler to be called. - return waitForHandler; - }).then(function(authEvent) { - // Unknown event triggered. - assertAuthEventEquals(noEvent, authEvent); - }); - }); -} - - -function testCordovaHandler_initialValidAuthEvent_direct() { - return installAndRunTest('initialValidAuthEvent_direct', function() { - // This should have been previously saved in a process redirect call. - var partialEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - 'SESSION_ID', - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // The final resolved event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - 'SESSION_ID'); - var handler; - var waitForHandler = new goog.Promise(function(resolve, reject) { - handler = resolve; - }); - // Trigger the universal link with the OAuth response on subscription. - universalLinks.subscribe = function(eventType, cb) { - cb({url: incomingUrl}); - }; - // Assume pending redirect event. - return savePartialEventManager.setAuthEvent(storageKey, partialEvent) - .then(function() { - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.initializeAndWait(); - }).then(function() { - // Wait for handler to be called. - return waitForHandler; - }).then(function(authEvent) { - // Auth event triggered. It should be the complete event. - assertAuthEventEquals(completeEvent, authEvent); - }); - }); -} - - -function testCordovaHandler_initialValidAuthEvent_link() { - return installAndRunTest('initialValidAuthEvent_link', function() { - // This should have been previously saved in a process redirect call. - var partialEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - 'SESSION_ID', - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var incomingUrl = - 'https://example.app.goo.gl/?link=' + - encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - // The expected event constructed from incoming link. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - 'SESSION_ID'); - var handler; - var waitForHandler = new goog.Promise(function(resolve, reject) { - handler = resolve; - }); - // Trigger the universal link with the OAuth response on subscription. - universalLinks.subscribe = function(eventType, cb) { - cb({url: incomingUrl}); - }; - // Assume pending redirect event. - return savePartialEventManager.setAuthEvent(storageKey, partialEvent) - .then(function() { - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.initializeAndWait(); - }).then(function() { - // Wait for handler to be called. - return waitForHandler; - }).then(function(authEvent) { - // Auth event triggered. The expected event should be dispatched. - assertAuthEventEquals(completeEvent, authEvent); - }); - }); -} - - -function testCordovaHandler_initialValidAuthEvent_deepLinkId() { - return installAndRunTest('initialValidAuthEvent_deepLinkId', function() { - // This should have been previously saved in a process redirect call. - var partialEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - 'SESSION_ID', - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var incomingUrl = - 'comexampleiosurl://google/link?deep_link_id=' + - encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - 'SESSION_ID'); - var handler; - var waitForHandler = new goog.Promise(function(resolve, reject) { - handler = resolve; - }); - // Trigger the universal link with the OAuth response on subscription. - universalLinks.subscribe = function(eventType, cb) { - cb({url: incomingUrl}); - }; - // Assume pending redirect event. - return savePartialEventManager.setAuthEvent(storageKey, partialEvent) - .then(function() { - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.initializeAndWait(); - }).then(function() { - // This should have been previously saved in a process redirect call. - return waitForHandler; - }).then(function(authEvent) { - // Auth event triggered. The expected event should be triggered. - assertAuthEventEquals(completeEvent, authEvent); - }); - }); -} - - -function testCordovaHandler_initialErrorAuthEvent() { - return installAndRunTest('initialErrorAuthEvent', function() { - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var partialEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - 'SESSION_ID', - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // The callback URL should contain the error. - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback?firebaseError=' + - JSON.stringify(expectedError.toPlainObject()); - // Triggered event with the error. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - null, - expectedError); - var handler; - var waitForHandler = new goog.Promise(function(resolve, reject) { - handler = resolve; - }); - // Trigger the universal link with the OAuth response on subscription. - universalLinks.subscribe = function(eventType, cb) { - cb({url: incomingUrl}); - }; - // Assume pending redirect event. - return savePartialEventManager.setAuthEvent(storageKey, partialEvent) - .then(function() { - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.initializeAndWait(); - }).then(function() { - // Wait for handler to trigger. - return waitForHandler; - }).then(function(authEvent) { - // Auth event triggered with the expected error event. - assertAuthEventEquals(completeEvent, authEvent); - }); - }); -} - - -function testCordovaHandler_processRedirect_success_android() { - return installAndRunTest('processRedirect_success_android', function() { - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }, - // Confirm expected endpoint ID passed. - fireauth.constants.Endpoint.STAGING.id); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial unknown event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function(url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({url: incomingUrl}); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10, undefined, - fireauth.constants.Endpoint.STAGING.id); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_success_android_tenantId() { - return installAndRunTest( - 'processRedirect_success_android_tenantId', function() { - var tenantId = '123456789012'; - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }, - // Confirm expected endpoint ID passed. - fireauth.constants.Endpoint.STAGING.id, - tenantId); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111', - null, - null, - tenantId); - // Initial unknown event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function(url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({url: incomingUrl}); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10, undefined, - fireauth.constants.Endpoint.STAGING.id); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234', tenantId) - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_success_parallelCalls() { - return installAndRunTest('processRedirect_success_parallelCalls', function() { - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL for first operation. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Construct OAuth handler URL for second operation. - var expectedUrl2 = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '5678', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_OPERATION_PENDING); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event for first completed call. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Completed event for second completed call. - var completeEvent2 = new fireauth.AuthEvent( - 'linkViaRedirect', - '5678', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial unknown event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - var openUrlCalls = 0; - cordova.plugins.browsertab.openUrl = function(url) { - openUrlCalls++; - // Confirm expected URL on each completed call. - if (openUrlCalls == 1) { - // First operation expected URL. - assertEquals(expectedUrl, url); - } else { - // Second operation expected URL. - assertEquals(expectedUrl2, url); - } - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({url: incomingUrl}); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - var successiveCallResult = null; - var p = cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // The other parallel operation should have failed with the expected - // error. - assertErrorEquals(expectedError, successiveCallResult); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - // Trigger again. As the operation already completed, this should - // eventually succeed too. - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '5678'); - }).then(function() { - // Confirm browsertab close called again. - assertEquals(2, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered thrice. - assertEquals(3, handler.getCallCount()); - // Last call should have resolved event with the second event. - assertAuthEventEquals( - completeEvent2, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - // This should fail as there is a pending operation already. - cordovaHandler.processRedirect('linkViaRedirect', provider, '5678') - .thenCatch(function(error) { - // Confirm browsertab close not called. - assertEquals(0, cordova.plugins.browsertab.close.getCallCount()); - // Save the pending redirect error. - successiveCallResult = error; - }); - return p; - }); -} - - -function testCordovaHandler_processRedirect_success_parallelCalls_tenantId() { - return installAndRunTest( - 'processRedirect_success_parallelCalls_tenantId', function() { - var tenantId1 = '123456789012'; - var tenantId2 = '987654321098'; - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL for first operation. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }, - null, - tenantId1); - // Construct OAuth handler URL for second operation. - var expectedUrl2 = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '5678', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }, - null, - tenantId2); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_OPERATION_PENDING); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event for first completed call. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111', - null, - null, - tenantId1); - // Completed event for second completed call. - var completeEvent2 = new fireauth.AuthEvent( - 'linkViaRedirect', - '5678', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111', - null, - null, - tenantId2); - // Initial unknown event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - var openUrlCalls = 0; - cordova.plugins.browsertab.openUrl = function(url) { - openUrlCalls++; - // Confirm expected URL on each completed call. - if (openUrlCalls == 1) { - // First operation expected URL. - assertEquals(expectedUrl, url); - } else { - // Second operation expected URL. - assertEquals(expectedUrl2, url); - } - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({url: incomingUrl}); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - var successiveCallResult = null; - var p = cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234', tenantId1) - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // The other parallel operation should have failed with the expected - // error. - assertErrorEquals(expectedError, successiveCallResult); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - // Trigger again. As the operation already completed, this should - // eventually succeed too. - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '5678', tenantId2); - }).then(function() { - // Confirm browsertab close called again. - assertEquals(2, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered thrice. - assertEquals(3, handler.getCallCount()); - // Last call should have resolved event with the second event. - assertAuthEventEquals( - completeEvent2, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - // This should fail as there is a pending operation already. - cordovaHandler.processRedirect('linkViaRedirect', provider, '5678') - .thenCatch(function(error) { - // Confirm browsertab close not called. - assertEquals(0, cordova.plugins.browsertab.close.getCallCount()); - // Save the pending redirect error. - successiveCallResult = error; - }); - return p; - }); -} - - -function testCordovaHandler_processRedirect_success_withEmulator() { - var emulatorConfig = { - url: 'http://emulator.test.domain:1234' - }; - return installAndRunTest( - 'testCordovaHandler_processRedirect_success_withEmulator', - function () { - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }, - // Confirm expected endpoint ID passed. - fireauth.constants.Endpoint.STAGING.id, - null, - emulatorConfig); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function () { - return 0; - }); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function () { - return androidUA; - }); - var incomingUrl = - 'http://emulator.test.domain:1234/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - incomingUrl, - '11111111111111111111'); - // Initial unknown event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function (eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function (url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({ url: incomingUrl }); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10, undefined, - fireauth.constants.Endpoint.STAGING.id, emulatorConfig); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function () { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function (event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_success_ios() { - return installAndRunTest('processRedirect_success_ios', function() { - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate iOS environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return iOS9iPhoneUA; - }); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial no event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function(url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - // This is currently not the default behavior but will be supported in the - // future. - savedCb({url: incomingUrl}); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_success_ios_tenantId() { - return installAndRunTest( - 'processRedirect_success_ios_tenantId', function() { - var tenantId = '123456789012'; - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }, - null, - tenantId); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate iOS environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return iOS9iPhoneUA; - }); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111', - null, - null, - tenantId); - // Initial no event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function(url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - // This is currently not the default behavior but will be supported in the - // future. - savedCb({url: incomingUrl}); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234', tenantId) - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_success_ios_custom() { - return installAndRunTest('processRedirect_success_ios_custom', function() { - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate iOS environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - // Even though this is iOS9, custom scheme redirects can still be - // used. - return iOS9iPhoneUA; - }); - // Valid custom scheme URL. - var incomingUrl = 'com.example.app://google/link?deep_link_id=' + - encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial no event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - // Not used. - }; - cordova.plugins.browsertab.openUrl = function(url) { - // browsertab available in iOS 9+. - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the custom scheme - // redirect callback with the OAuth response. - goog.global['handleOpenURL'](incomingUrl); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_success_ios_custom_tenantId() { - return installAndRunTest( - 'processRedirect_success_ios_custom_tenantId', function() { - var tenantId = '123456789012'; - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }, - null, - tenantId); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate iOS environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - // Even though this is iOS9, custom scheme redirects can still be - // used. - return iOS9iPhoneUA; - }); - // Valid custom scheme URL. - var incomingUrl = 'com.example.app://google/link?deep_link_id=' + - encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111', - null, - null, - tenantId); - // Initial no event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - // Not used. - }; - cordova.plugins.browsertab.openUrl = function(url) { - // browsertab available in iOS 9+. - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the custom scheme - // redirect callback with the OAuth response. - goog.global['handleOpenURL'](incomingUrl); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234', tenantId) - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_success_ios_caseInsensitive() { - return installAndRunTest( - 'processRedirect_success_ios_caseInsensitive', function() { - // Use an upper case character in the bundle ID. This should not matter. - BuildInfo.packageName = 'com.example.App'; - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.App', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate iOS environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - // Even though this is iOS9, custom scheme redirects can still be - // used. - return iOS9iPhoneUA; - }); - // Valid custom scheme URL. For some reason, the scheme is lower cased. - var incomingUrl = 'com.example.app://google/link?deep_link_id=' + - encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial no event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - // Not used. - }; - cordova.plugins.browsertab.openUrl = function(url) { - // browsertab available in iOS 9+. - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the custom scheme - // redirect callback with the OAuth response. - goog.global['handleOpenURL'](incomingUrl); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - // This should still work. - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_browserTabUnavailable() { - return installAndRunTest('processRedirect_browserTabUnavailable', function() { - // Test when browser tab is not available, inappbrowser should be used and - // a system browser opened. - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial unknown event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - var expectedInAppBrowserType = '_system'; - var expectedInAppBrowserOptions = 'location=yes'; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - // Simulate browser tab not available. - cordova.plugins.browsertab.isAvailable = function(cb) { - cb(false); - }; - // Should not be called. - cordova.plugins.browsertab.openUrl = function(url) { - fail('browsertab.openUrl should not call!'); - }; - // InAppBrowser opener should be called as browsertab is unavailable. - cordova.InAppBrowser.open = function(url, type, options) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // Confirm system browser used as this is Android. - assertEquals(expectedInAppBrowserType, type); - // Confirm expected options. - assertEquals(expectedInAppBrowserOptions, options); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({url: incomingUrl}); - // Cannot close a system browser. - return null; - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_browserTabUnavailable_tenantId() { - return installAndRunTest('processRedirect_browserTabUnavailable', function() { - // Test when browser tab is not available, inappbrowser should be used and - // a system browser opened. - var tenantId = '123456789012'; - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }, - null, - tenantId); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111', - null, - null, - tenantId); - // Initial unknown event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - var expectedInAppBrowserType = '_system'; - var expectedInAppBrowserOptions = 'location=yes'; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - // Simulate browser tab not available. - cordova.plugins.browsertab.isAvailable = function(cb) { - cb(false); - }; - // Should not be called. - cordova.plugins.browsertab.openUrl = function(url) { - fail('browsertab.openUrl should not call!'); - }; - // InAppBrowser opener should be called as browsertab is unavailable. - cordova.InAppBrowser.open = function(url, type, options) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // Confirm system browser used as this is Android. - assertEquals(expectedInAppBrowserType, type); - // Confirm expected options. - assertEquals(expectedInAppBrowserOptions, options); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({url: incomingUrl}); - // Cannot close a system browser. - return null; - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234', tenantId) - .then(function() { - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_webview_withNoHandler() { - return installAndRunTest('processRedirect_webview_withNoHandler', function() { - // This will test embedded webview when handleOpenURL is not defined by the - // developer. Emebedded webviews are used in iOS8 and under and need to be - // closed explicitly. - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate iOS 8 environment where SFSafariViewController is not supported. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return iOS8iPhoneUA; - }); - // Valid custom scheme URL. - var incomingUrl = 'com.example.app://google/link?deep_link_id=' + - encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial no event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var expectedInAppBrowserType = '_blank'; - var expectedInAppBrowserOptions = 'location=yes'; - var inAppBrowserWindowRef = { - close: goog.testing.recordFunction() - }; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - // Not used. - }; - // Simulate browser tab not available. - cordova.plugins.browsertab.isAvailable = function(cb) { - cb(false); - }; - // Should not be called. - cordova.plugins.browsertab.openUrl = function(url) { - fail('browsertab.openUrl should not call!'); - }; - // InAppBrowser opener should be called as browsertab is unavailable. - cordova.InAppBrowser.open = function(url, type, options) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // Confirm embedded webview browser used as this is iOS8. - assertEquals(expectedInAppBrowserType, type); - // Confirm expected options. - assertEquals(expectedInAppBrowserOptions, options); - // Custom schemes should be used here. - // This should be handled. - goog.global['handleOpenURL'](incomingUrl); - // Emebdded webview can be closed. - return inAppBrowserWindowRef; - }; - // handleOpenURL is not defined here. - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm inappbrowser window close called. - assertEquals(1, inAppBrowserWindowRef.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_webview_withHandler() { - return installAndRunTest('processRedirect_webview_withHandler', function() { - // This will test embedded webview when handleOpenURL is already defined by - // the developer. Embedded webviews are used in iOS8 and under and need to - // be closed explicitly. - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate iOS 8 environment where SFSafariViewController is not supported. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return iOS8iPhoneUA; - }); - // Invalid URL that should be ignored. - var invalidUrl1 = - 'http://example.firebaseapp.com/__/auth/callback#invalid'; - var invalidUrl2 = 'comexampleapp://google/link?deep_link_id=' + - encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#invalid'); - // Valid custom scheme URL. - var incomingUrl = 'com.example.app://google/link?deep_link_id=' + - encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial no event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var expectedInAppBrowserType = '_blank'; - var expectedInAppBrowserOptions = 'location=yes'; - var inAppBrowserWindowRef = { - close: goog.testing.recordFunction() - }; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - // Not used. - }; - // Simulate browser tab not available. - cordova.plugins.browsertab.isAvailable = function(cb) { - cb(false); - }; - // Should not be called. - cordova.plugins.browsertab.openUrl = function(url) { - fail('browsertab.openUrl should not call!'); - }; - // InAppBrowser opener should be called as browsertab is unavailable. - cordova.InAppBrowser.open = function(url, type, options) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // Confirm embedded webview browser used as this is iOS8. - assertEquals(expectedInAppBrowserType, type); - // Confirm expected options. - assertEquals(expectedInAppBrowserOptions, options); - // Custom schemes should be used here. - // Invalid URLs should be ignored. - goog.global['handleOpenURL'](invalidUrl1); - assertEquals(1, recordedFunction.getCallCount()); - assertEquals(invalidUrl1, recordedFunction.getLastCall().getArgument(0)); - goog.global['handleOpenURL'](invalidUrl2); - assertEquals(2, recordedFunction.getCallCount()); - assertEquals(invalidUrl2, recordedFunction.getLastCall().getArgument(0)); - // This should be handled. - goog.global['handleOpenURL'](incomingUrl); - assertEquals(3, recordedFunction.getCallCount()); - assertEquals(incomingUrl, recordedFunction.getLastCall().getArgument(0)); - // Emebdded webview can be closed. - return inAppBrowserWindowRef; - }; - // Assume developer already using custom scheme plugin. Make sure - // their handler is not overwritten. - var recordedFunction = goog.testing.recordFunction(); - goog.global['handleOpenURL'] = recordedFunction; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm inappbrowser window close called. - assertEquals(1, inAppBrowserWindowRef.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_doubleDeepLink() { - return installAndRunTest('processRedirect_doubleDeepLink', function() { - // Tests when the incoming URL has another deep link embedded. - // This happens when the auto redirect to FDL is intercepted by the app. - // Another deep link is contained within in case it is not intercepted and - // the page can then display the FDL button using the deep link embedded. - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - // The automatic FDL redirect link in Android will have the following - // format if intercepted by the app. - var deepLink = 'http://example.firebaseapp.com/__/auth/callback' + - '/?link=' + encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - var incomingUrl = - 'https://example.app.goo.gl/?link=' + encodeURIComponent(deepLink); - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial unknown event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function(url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({url: incomingUrl}); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_ios7or8doubleDeepLink() { - return installAndRunTest('processRedirect_ios7or8doubleDeepLink', function() { - // Tests when the incoming URL has another deep link embedded. - // This happens when the auto redirect to FDL is intercepted by the app. - // Another deep link is contained within in case it is not intercepted and - // the page can then display the FDL button using the deep link embedded. - // This tests the flow in iOS 7 or 8 where custom URL schemes are used. - // Realistically, this is not needed as custom URL scheme redirects do not - // requires clicks. - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Simulate iOS environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return iOS8iPhoneUA; - }); - // This would happen if auto redirect for iOS 8 using custom schemes - // contains a double link. In reality, this is not needed as custom scheme - // redirects always get intercepted by an app without an additional click. - var deepLink = 'http://example.firebaseapp.com/__/auth/callback' + - '/?link=' + encodeURIComponent( - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'); - var incomingUrl = 'com.example.app://google/link?deep_link_id=' + - encodeURIComponent(deepLink); - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Initial no event. - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var expectedInAppBrowserType = '_blank'; - var expectedInAppBrowserOptions = 'location=yes'; - var inAppBrowserWindowRef = { - close: goog.testing.recordFunction() - }; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - // Not used. - }; - // Simulate browser tab not available. - cordova.plugins.browsertab.isAvailable = function(cb) { - cb(false); - }; - // Should not be called. - cordova.plugins.browsertab.openUrl = function(url) { - fail('browsertab.openUrl should not call!'); - }; - // InAppBrowser opener should be called as browsertab is unavailable. - cordova.InAppBrowser.open = function(url, type, options) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // Confirm embedded webview browser used as this is iOS8. - assertEquals(expectedInAppBrowserType, type); - // Confirm expected options. - assertEquals(expectedInAppBrowserOptions, options); - // Custom schemes should be used here. - // This should be handled. - goog.global['handleOpenURL'](incomingUrl); - // Emebdded webview can be closed. - return inAppBrowserWindowRef; - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_invalidProvider() { - return installAndRunTest('processRedirect_invalidProvider', function() { - // Invalid OAuth provider. - var provider = new fireauth.EmailAuthProvider(); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - // Confirm browsertab close not called. - assertEquals(0, cordova.plugins.browsertab.close.getCallCount()); - // Handler not triggered due to invalid provider error. - assertEquals(0, handler.getCallCount()); - // Expected invalid provider error. - assertErrorEquals(expectedError, error); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_error_parallelCalls() { - return installAndRunTest('processRedirect_error_parallelCalls', function() { - // Invalid OAuth provider. - var provider = new fireauth.EmailAuthProvider(); - // Expected invalid provider error. - var expectedInvalidProviderError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - // Expected error when there is a pending redirect operation. - var expectedProcessRedirectError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_OPERATION_PENDING); - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - var p = cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - // The other parallel operation should have failed with the expected - // pending redirect error. - assertErrorEquals(expectedProcessRedirectError, successiveCallResult); - // Handler not triggered due to invalid provider error. - assertEquals(0, handler.getCallCount()); - // Expected invalid provider error. - assertErrorEquals(expectedInvalidProviderError, error); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - // Call again, this should throw the expected error and complete. - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234'); - }).then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - // Handler not triggered due to invalid provider error. - assertEquals(0, handler.getCallCount()); - // Expected invalid provider error. - assertErrorEquals(expectedInvalidProviderError, error); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - // Confirm browsertab close not called. - assertEquals(0, cordova.plugins.browsertab.close.getCallCount()); - assertNull(event); - }); - // This should fail as there is a pending operation already. - cordovaHandler.processRedirect('linkViaRedirect', provider, '5678') - .thenCatch(function(error) { - // Confirm browsertab close not called. - assertEquals(0, cordova.plugins.browsertab.close.getCallCount()); - successiveCallResult = error; - }); - return p; - }); -} - - -function testCordovaHandler_processRedirect_error() { - return installAndRunTest('processRedirect_error', function() { - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - // Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - // Append error to callback URL. - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback?firebaseError=' + - JSON.stringify(expectedError.toPlainObject()); - // Completed event with error. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - null, - null, expectedError); - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function(url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - // On openUrl, simulate completion by triggering the universal link - // callback with the OAuth response. - savedCb({url: incomingUrl}); - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10); - cordovaHandler.addAuthEventListener(handler); - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice. - assertEquals(2, handler.getCallCount()); - // First with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Then with resolved event. In this case, this contains an error. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_redirectCancelledError() { - return installAndRunTest('processRedirect_redirectCancelledErr', function() { - var doc = goog.global.document; - stubs.replace( - doc, - 'addEventListener', - function(eventName, onResume) { - // Trigger on resume event. This will eventually trigger the redirect - // cancelled by user error after it times out. - if (eventName == 'resume') { - onResume(); - } - }); - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - apn: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_CANCELLED_BY_USER); - // Android environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return androidUA; - }); - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // openUrl call counter. - var openUrlCounter = 0; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function(url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - openUrlCounter++; - // On second call, succeed. - if (openUrlCounter == 2) { - savedCb({url: incomingUrl}); - } - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10, 10); - cordovaHandler.addAuthEventListener(handler); - // Test all possible scenarios: - // 1. redirect operation is cancelled. - // 2. redirect operation called again successfully. - // 3. redirect operation called again and cancelled. - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - // Confirm browsertab close not called. - assertEquals(0, cordova.plugins.browsertab.close.getCallCount()); - // Handler should be triggered only for first event. - assertEquals(1, handler.getCallCount()); - // Handler called with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Expected cancellation error. - assertErrorEquals(expectedError, error); - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - // Try again, this time it should succeed. - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234'); - }).then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice at this point. - assertEquals(2, handler.getCallCount()); - // Handler last call will be with the completed event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - // Try again, this time it will be cancelled again. - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234'); - }).then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - // Confirm browsertab close not called again. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler should not be triggered any more. - assertEquals(2, handler.getCallCount()); - // Expected cancellation error. - assertErrorEquals(expectedError, error); - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} - - -function testCordovaHandler_processRedirect_visibilityChange_cancelledErr() { - return installAndRunTest('processRedirect_visibility_cancelErr', function() { - // Test visibilitychange event in iOS where resume does not trigger in the - // case of SFSVC, - var doc = goog.global.document; - // Stub fireauth.util.isAppVisible() to return true. - stubs.replace( - fireauth.util, - 'isAppVisible', - function() { - return true; - }); - stubs.replace( - doc, - 'addEventListener', - function(eventName, onVisibilityChange) { - // We will ignore resume event and only trigger visibilityChange. - if (eventName == 'visibilitychange') { - onVisibilityChange(); - } - }); - var provider = new fireauth.GoogleAuthProvider(); - // Construct OAuth handler URL. - var expectedUrl = - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - null, - '1234', - version, - { - ibi: 'com.example.app', - appDisplayName: 'Test App', - sessionId: sha256('11111111111111111111') - }); - var incomingUrl = - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse'; - // Completed event. - var completeEvent = new fireauth.AuthEvent( - 'linkViaRedirect', - '1234', - 'http://example.firebaseapp.com/__/auth/callback#oauthResponse', - '11111111111111111111'); - // Stub this so the session ID generated can be predictable. - stubs.replace( - Math, - 'random', - function() { - return 0; - }); - // Expected error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.REDIRECT_CANCELLED_BY_USER); - // iOS environment. - stubs.replace( - fireauth.util, - 'getUserAgentString', - function() { - return iOS9iPhoneUA; - }); - var noEvent = new fireauth.AuthEvent( - fireauth.AuthEvent.Type.UNKNOWN, - null, - null, - null, - new fireauth.AuthError(fireauth.authenum.Error.NO_AUTH_EVENT)); - var savedCb = null; - // openUrl call counter. - var openUrlCounter = 0; - // Save the universal link callback on subscription. - universalLinks.subscribe = function(eventType, cb) { - savedCb = cb; - }; - cordova.plugins.browsertab.openUrl = function(url) { - // Confirm expected URL. - assertEquals(expectedUrl, url); - openUrlCounter++; - // On second call, succeed. - if (openUrlCounter == 2) { - savedCb({url: incomingUrl}); - } - }; - var handler = goog.testing.recordFunction(); - cordovaHandler = new fireauth.CordovaHandler( - authDomain, apiKey, appName, version, 10, 10); - cordovaHandler.addAuthEventListener(handler); - // Test all possible scenarios: - // 1. redirect operation is cancelled. - // 2. redirect operation called again successfully. - // 3. redirect operation called again and cancelled. - return cordovaHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - // Confirm browsertab close not called. - assertEquals(0, cordova.plugins.browsertab.close.getCallCount()); - // Handler should be triggered only for first event. - assertEquals(1, handler.getCallCount()); - // Handler called with no event. - assertAuthEventEquals( - noEvent, - handler.getCalls()[0].getArgument(0)); - // Expected cancellation error. - assertErrorEquals(expectedError, error); - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - // Try again, this time it should succeed. - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234'); - }).then(function() { - // Confirm browsertab close called. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler triggered twice at this point. - assertEquals(2, handler.getCallCount()); - // Handler last call will be with the completed event. - assertAuthEventEquals( - completeEvent, - handler.getLastCall().getArgument(0)); - // Confirm event deleted from storage. - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - // Try again, this time it will be cancelled again. - return cordovaHandler.processRedirect( - 'linkViaRedirect', provider, '1234'); - }).then(function() { - throw new Error('Unexpected success!'); - }, function(error) { - // Confirm browsertab close not called again. - assertEquals(1, cordova.plugins.browsertab.close.getCallCount()); - // Handler should not be triggered any more. - assertEquals(2, handler.getCallCount()); - // Expected cancellation error. - assertErrorEquals(expectedError, error); - return getAndDeletePartialEventManager.getAuthEvent(); - }).then(function(event) { - assertNull(event); - }); - }); -} diff --git a/packages/auth/test/defines_test.js b/packages/auth/test/defines_test.js deleted file mode 100644 index eb3c554d6a6..00000000000 --- a/packages/auth/test/defines_test.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for defines.js. - */ - -goog.provide('fireauth.constantsTest'); - -goog.require('fireauth.constants'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.constantsTest'); - - -function testGetEndpointConfig() { - var boqEndpoint = fireauth.constants.Endpoint.BOQ; - var productionEndpoint = fireauth.constants.Endpoint.PRODUCTION; - var stagingEndpoint = fireauth.constants.Endpoint.STAGING; - var testEndpoint = fireauth.constants.Endpoint.TEST; - assertObjectEquals( - { - 'firebaseEndpoint': boqEndpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': boqEndpoint.secureTokenEndpoint, - 'identityPlatformEndpoint': boqEndpoint.identityPlatformEndpoint - }, - fireauth.constants.getEndpointConfig( - fireauth.constants.Endpoint.BOQ.id)); - assertObjectEquals( - { - 'firebaseEndpoint': productionEndpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': productionEndpoint.secureTokenEndpoint, - 'identityPlatformEndpoint': productionEndpoint.identityPlatformEndpoint - }, - fireauth.constants.getEndpointConfig( - fireauth.constants.Endpoint.PRODUCTION.id)); - assertObjectEquals( - { - 'firebaseEndpoint': stagingEndpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': stagingEndpoint.secureTokenEndpoint, - 'identityPlatformEndpoint': stagingEndpoint.identityPlatformEndpoint - }, - fireauth.constants.getEndpointConfig( - fireauth.constants.Endpoint.STAGING.id)); - assertObjectEquals( - { - 'firebaseEndpoint': testEndpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': testEndpoint.secureTokenEndpoint, - 'identityPlatformEndpoint': testEndpoint.identityPlatformEndpoint - }, - fireauth.constants.getEndpointConfig( - fireauth.constants.Endpoint.TEST.id)); - assertNull(fireauth.constants.getEndpointConfig()); - assertNull(fireauth.constants.getEndpointConfig(null)); - assertNull(fireauth.constants.getEndpointConfig(undefined)); - assertNull(fireauth.constants.getEndpointConfig('invalid')); -} - - -function testGetEndpointId() { - assertEquals( - fireauth.constants.Endpoint.PRODUCTION.id, - fireauth.constants.getEndpointId( - fireauth.constants.Endpoint.PRODUCTION.id)); - assertEquals( - fireauth.constants.Endpoint.STAGING.id, - fireauth.constants.getEndpointId( - fireauth.constants.Endpoint.STAGING.id)); - assertEquals( - fireauth.constants.Endpoint.TEST.id, - fireauth.constants.getEndpointId( - fireauth.constants.Endpoint.TEST.id)); - assertUndefined(fireauth.constants.getEndpointId()); - assertUndefined(fireauth.constants.getEndpointId(null)); - assertUndefined(fireauth.constants.getEndpointId(undefined)); - assertUndefined(fireauth.constants.getEndpointId('invalid')); -} diff --git a/packages/auth/test/deprecation_test.js b/packages/auth/test/deprecation_test.js deleted file mode 100644 index 012b2c6e326..00000000000 --- a/packages/auth/test/deprecation_test.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.deprecationTest'); - -goog.require('fireauth.deprecation'); -goog.require('fireauth.deprecation.Deprecations'); -goog.require('fireauth.util'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.deprecationTest'); - - -var mockControl; -var mockLogger; - -// Add fake deprecation notices to the list of possible notices. -fireauth.deprecation.Deprecations.TEST_MESSAGE = 'This is a test.'; -fireauth.deprecation.Deprecations.TEST_MESSAGE_2 = 'This is another test.'; - - -function setUp() { - mockControl = new goog.testing.MockControl(); - mockWarning = mockControl.createMethodMock(fireauth.util, 'consoleWarn'); -} - - -function tearDown() { - mockControl.$verifyAll(); - mockControl.$tearDown(); - fireauth.deprecation.resetForTesting(); -} - - -function testLog() { - mockWarning(fireauth.deprecation.Deprecations.TEST_MESSAGE).$once(); - - mockControl.$replayAll(); - - fireauth.deprecation.log(fireauth.deprecation.Deprecations.TEST_MESSAGE); -} - - -function testLogMultiple() { - mockWarning(fireauth.deprecation.Deprecations.TEST_MESSAGE).$once(); - mockWarning(fireauth.deprecation.Deprecations.TEST_MESSAGE_2).$once(); - - mockControl.$replayAll(); - - fireauth.deprecation.log(fireauth.deprecation.Deprecations.TEST_MESSAGE); - fireauth.deprecation.log(fireauth.deprecation.Deprecations.TEST_MESSAGE_2); -} - - -function testLogMultipleDontRepeat() { - mockWarning(fireauth.deprecation.Deprecations.TEST_MESSAGE).$once(); - mockWarning(fireauth.deprecation.Deprecations.TEST_MESSAGE_2).$once(); - - mockControl.$replayAll(); - - fireauth.deprecation.log(fireauth.deprecation.Deprecations.TEST_MESSAGE); - fireauth.deprecation.log(fireauth.deprecation.Deprecations.TEST_MESSAGE_2); - fireauth.deprecation.log(fireauth.deprecation.Deprecations.TEST_MESSAGE); - fireauth.deprecation.log(fireauth.deprecation.Deprecations.TEST_MESSAGE_2); - fireauth.deprecation.log(fireauth.deprecation.Deprecations.TEST_MESSAGE_2); -} diff --git a/packages/auth/test/dynamiclink_test.js b/packages/auth/test/dynamiclink_test.js deleted file mode 100644 index c1bee0882ce..00000000000 --- a/packages/auth/test/dynamiclink_test.js +++ /dev/null @@ -1,361 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for dynamiclink.js. - */ - -goog.provide('fireauth.DynamicLinkTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.DynamicLink'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.util'); -goog.require('goog.Uri'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.DynamicLinkTest'); - - -var customSchemeLink; -var reverseOAuthClientIdCustomSchemeLink; -var customSchemeLinkUrl; -var reverseOAuthClientIdCustomSchemeLinkUrl; -var androidDynamicLink; -var iosDynamicLink; -var androidDynamicLinkUrl; -var clientId = '123456.apps.googleusercontent.com'; -var fdlDomain = 'example.app.goo.gl'; -var androidPlatform = fireauth.DynamicLink.Platform.ANDROID; -var iosPlatform = fireauth.DynamicLink.Platform.IOS; -var appIdentifier = 'com.example.application'; -var authDomain = 'example.firebaseapp.com'; -var payload = 'https://example.firebaseapp.com/__/auth/callback#oauthResponse'; -var defaultError = - new fireauth.AuthError(fireauth.authenum.Error.APP_NOT_INSTALLED); -var appName = 'Hello World!'; -var customFallbackUrl = 'https://example.firebaseapp.com/custom/fallback'; -var fallbackUrl; -var androidAutoRedirectDynamicLinkUrl; -var androidUserInteractionDynamicLinkUrl; -var noAppNameAndroidDynamicLinkUrl; -var noAppNameAndroidAutoRedirectDynamicLinkUrl; -var requiredDynamicLinkUrlFields = [ - 'fdlDomain', 'platform', 'appIdentifier', 'authDomain', 'link', 'appName']; - - -function setUp() { - fallbackUrl = 'https://' + authDomain + '/__/auth/handler?firebaseError=' + - encodeURIComponent(/** @type {string} */ ( - fireauth.util.stringifyJSON(defaultError.toPlainObject()))); - androidDynamicLinkUrl = 'https://example.firebaseapp.com/__/auth/callback?' + - 'fdlDomain=' + encodeURIComponent(fdlDomain) + - '&platform=' + encodeURIComponent(androidPlatform) + - '&appIdentifier=' + encodeURIComponent(appIdentifier) + - '&authDomain=' + encodeURIComponent(authDomain) + - '&link=' + encodeURIComponent(payload) + - '&appName=' + encodeURIComponent(appName); - noAppNameAndroidDynamicLinkUrl = - goog.Uri.parse(androidDynamicLinkUrl).setParameterValue('appName', '') - .toString(); - androidUserInteractionDynamicLinkUrl = 'https://' + fdlDomain + '/?' + - 'apn=' + encodeURIComponent(appIdentifier) + - '&afl=' + encodeURIComponent(fallbackUrl) + - '&link=' + encodeURIComponent(payload); - androidAutoRedirectDynamicLinkUrl = 'https://' + fdlDomain + '/?' + - 'apn=' + encodeURIComponent(appIdentifier) + - '&afl=' + encodeURIComponent(androidDynamicLinkUrl) + - '&link=' + encodeURIComponent(androidDynamicLinkUrl); - noAppNameAndroidAutoRedirectDynamicLinkUrl = 'https://' + fdlDomain + '/?' + - 'apn=' + encodeURIComponent(appIdentifier) + - '&afl=' + encodeURIComponent(noAppNameAndroidDynamicLinkUrl) + - '&link=' + encodeURIComponent(noAppNameAndroidDynamicLinkUrl); - iosDynamicLinkUrl = 'https://example.firebaseapp.com/__/auth/callback?' + - 'fdlDomain=' + encodeURIComponent(fdlDomain) + - '&platform=' + encodeURIComponent(iosPlatform) + - '&appIdentifier=' + encodeURIComponent(appIdentifier) + - '&authDomain=' + encodeURIComponent(authDomain) + - '&link=' + encodeURIComponent(payload) + - '&appName=' + encodeURIComponent(appName); - iosUserInteractionDynamicLinkUrl = 'https://' + fdlDomain + '/?' + - 'ibi=' + encodeURIComponent(appIdentifier) + - '&ifl=' + encodeURIComponent(fallbackUrl) + - '&link=' + encodeURIComponent(payload); - iosAutoRedirectDynamicLinkUrl = 'https://' + fdlDomain + '/?' + - 'ibi=' + encodeURIComponent(appIdentifier) + - '&ifl=' + encodeURIComponent(iosDynamicLinkUrl) + - '&link=' + encodeURIComponent(iosDynamicLinkUrl); - customSchemeLinkUrl = appIdentifier + '://google/link?deep_link_id=' + - encodeURIComponent(payload); - reverseOAuthClientIdCustomSchemeLinkUrl = - 'com.googleusercontent.apps.123456://firebaseauth/link?deep_link_id=' + - encodeURIComponent(payload); -} - - -function tearDown() { - androidDynamicLink = null; - androidDynamicLinkUrl = null; - androidAutoRedirectDynamicLinkUrl = null; - androidUserInteractionDynamicLinkUrl = null; - iosDynamicLink = null; - iosDynamicLinkUrl = null; - iosAutoRedirectDynamicLinkUrl = null; - iosUserInteractionDynamicLinkUrl = null; - noAppNameAndroidDynamicLinkUrl = null; - noAppNameAndroidAutoRedirectDynamicLinkUrl = null; - customSchemeLink = null; - customSchemeLinkUrl = null; - reverseOAuthClientIdCustomSchemeLink = null; - reverseOAuthClientIdCustomSchemeLinkUrl = null; -} - - -function testDynamicLink_initialization() { - // Initialize the dynamic link. - androidDynamicLink = new fireauth.DynamicLink( - fdlDomain, androidPlatform, appIdentifier, authDomain, payload); - // Confirm all properties set correctly. - assertEquals(fallbackUrl, androidDynamicLink['fallbackUrl']); - assertEquals(fdlDomain, androidDynamicLink['fdlDomain']); - assertEquals(androidPlatform, androidDynamicLink['platform']); - assertEquals(appIdentifier, androidDynamicLink['appIdentifier']); - assertEquals(authDomain, androidDynamicLink['authDomain']); - assertEquals(payload, androidDynamicLink['payload']); - assertNull(androidDynamicLink['appName']); - assertNull(androidDynamicLink['clientId']); - // Set app name. - androidDynamicLink.setAppName(appName); - assertEquals(appName, androidDynamicLink['appName']); - // Override the default fallback URL. - androidDynamicLink.setFallbackUrl(customFallbackUrl); - assertEquals(customFallbackUrl, androidDynamicLink['fallbackUrl']); -} - - -function testDynamicLink_initialization_noFdlDomain() { - // Initialize the dynamic link. - customSchemeLink = new fireauth.DynamicLink( - null, iosPlatform, appIdentifier, authDomain, payload); - // Confirm all properties set correctly. - assertEquals(fallbackUrl, customSchemeLink['fallbackUrl']); - assertNull(customSchemeLink['fdlDomain']); - assertEquals(iosPlatform, customSchemeLink['platform']); - assertEquals(appIdentifier, customSchemeLink['appIdentifier']); - assertEquals(authDomain, customSchemeLink['authDomain']); - assertEquals(payload, customSchemeLink['payload']); - assertNull(customSchemeLink['appName']); - assertNull(customSchemeLink['clientId']); - // Set app name. - customSchemeLink.setAppName(appName); - assertEquals(appName, customSchemeLink['appName']); - // Override the default fallback URL. - customSchemeLink.setFallbackUrl(customFallbackUrl); - assertEquals(customFallbackUrl, customSchemeLink['fallbackUrl']); -} - - -function testDynamicLink_initialization_clientIdAndNoFdlDomain() { - // Initialize the dynamic link. - reverseOAuthClientIdCustomSchemeLink = new fireauth.DynamicLink( - null, iosPlatform, appIdentifier, authDomain, payload, clientId); - // Confirm all properties set correctly. - assertEquals( - fallbackUrl, reverseOAuthClientIdCustomSchemeLink['fallbackUrl']); - assertNull(reverseOAuthClientIdCustomSchemeLink['fdlDomain']); - assertEquals(iosPlatform, reverseOAuthClientIdCustomSchemeLink['platform']); - assertEquals( - appIdentifier, reverseOAuthClientIdCustomSchemeLink['appIdentifier']); - assertEquals(authDomain, reverseOAuthClientIdCustomSchemeLink['authDomain']); - assertEquals(payload, reverseOAuthClientIdCustomSchemeLink['payload']); - assertEquals(clientId, reverseOAuthClientIdCustomSchemeLink['clientId']); - assertNull(reverseOAuthClientIdCustomSchemeLink['appName']); - // Set app name. - reverseOAuthClientIdCustomSchemeLink.setAppName(appName); - assertEquals(appName, reverseOAuthClientIdCustomSchemeLink['appName']); - // Override the default fallback URL. - reverseOAuthClientIdCustomSchemeLink.setFallbackUrl(customFallbackUrl); - assertEquals( - customFallbackUrl, reverseOAuthClientIdCustomSchemeLink['fallbackUrl']); -} - - -function testDynamicLink_fromURL() { - // Invalid dynamic link. - assertNull(fireauth.DynamicLink.fromURL(payload)); - // Valid Android dynamic link. - androidDynamicLink = new fireauth.DynamicLink( - fdlDomain, androidPlatform, appIdentifier, authDomain, payload); - androidDynamicLink.setAppName(appName); - assertObjectEquals( - androidDynamicLink, fireauth.DynamicLink.fromURL(androidDynamicLinkUrl)); - // Valid iOS dynamic link. - iosDynamicLink = new fireauth.DynamicLink( - fdlDomain, iosPlatform, appIdentifier, authDomain, payload); - iosDynamicLink.setAppName(appName); - assertObjectEquals( - iosDynamicLink, fireauth.DynamicLink.fromURL(iosDynamicLinkUrl)); - // Any missing field should resolve to null. - for (var i = 0; i < requiredDynamicLinkUrlFields.length; i++) { - // Remove one required field and confirm it resolves to null. - assertNull(fireauth.DynamicLink.fromURL( - goog.Uri.parse(androidDynamicLinkUrl) - .removeParameter(requiredDynamicLinkUrlFields[i]) - .toString())); - } - // Custom scheme URLs can't be constructed from URL string. - assertNull(fireauth.DynamicLink.fromURL(customSchemeLinkUrl)); - assertNull(fireauth.DynamicLink.fromURL( - reverseOAuthClientIdCustomSchemeLinkUrl)); -} - - -function testDynamicLink_toString_isAutoRedirect_android() { - androidDynamicLink = new fireauth.DynamicLink( - fdlDomain, androidPlatform, appIdentifier, authDomain, payload); - androidDynamicLink.setAppName(appName); - assertEquals( - androidAutoRedirectDynamicLinkUrl, androidDynamicLink.toString(true)); -} - - -function testDynamicLink_toString_isAutoRedirect_android_noAppName() { - androidDynamicLink = new fireauth.DynamicLink( - fdlDomain, androidPlatform, appIdentifier, authDomain, payload); - // Assume no app name provided. - androidDynamicLink.setAppName(null); - assertEquals( - noAppNameAndroidAutoRedirectDynamicLinkUrl, - androidDynamicLink.toString(true)); -} - - -function testDynamicLink_toString_isAutoRedirect_ios() { - iosDynamicLink = new fireauth.DynamicLink( - fdlDomain, iosPlatform, appIdentifier, authDomain, payload); - iosDynamicLink.setAppName(appName); - assertEquals(iosAutoRedirectDynamicLinkUrl, iosDynamicLink.toString(true)); -} - - -function testDynamicLink_toString_isNotAutoRedirect_android() { - androidDynamicLink = new fireauth.DynamicLink( - fdlDomain, androidPlatform, appIdentifier, authDomain, payload); - androidDynamicLink.setAppName(appName); - assertEquals( - androidUserInteractionDynamicLinkUrl, androidDynamicLink.toString()); -} - - -function testDynamicLink_toString_isNotAutoRedirect_ios() { - iosDynamicLink = new fireauth.DynamicLink( - fdlDomain, iosPlatform, appIdentifier, authDomain, - payload); - iosDynamicLink.setAppName(appName); - assertEquals( - iosUserInteractionDynamicLinkUrl, iosDynamicLink.toString()); -} - - -function testDynamicLink_toString_customSchemeUrl() { - customSchemeLink = new fireauth.DynamicLink( - null, iosPlatform, appIdentifier, authDomain, payload); - customSchemeLink.setAppName(appName); - assertEquals( - customSchemeLinkUrl, customSchemeLink.toString(false)); - assertEquals( - customSchemeLinkUrl, customSchemeLink.toString(true)); -} - - -function testDynamicLink_toString_reverseOAuthClientIdCustomSchemeUrl() { - reverseOAuthClientIdCustomSchemeLink = new fireauth.DynamicLink( - null, iosPlatform, appIdentifier, authDomain, payload, clientId); - reverseOAuthClientIdCustomSchemeLink.setAppName(appName); - assertEquals( - reverseOAuthClientIdCustomSchemeLinkUrl, - reverseOAuthClientIdCustomSchemeLink.toString(false)); - assertEquals( - reverseOAuthClientIdCustomSchemeLinkUrl, - reverseOAuthClientIdCustomSchemeLink.toString(true)); -} - - -function testDynamicLink_parseDeepLink_urlItself() { - var deepLink = - 'https://example.firebaseapp.com/__/auth/callback#oauthResponse'; - assertEquals(deepLink, fireauth.DynamicLink.parseDeepLink(deepLink)); -} - - -function testDynamicLink_parseDeepLink_deepLink() { - var deepLink = - 'https://example.firebaseapp.com/__/auth/callback#oauthResponse'; - var url = 'https://example.app.goo.gl/?link=' + encodeURIComponent(deepLink); - assertEquals(deepLink, fireauth.DynamicLink.parseDeepLink(url)); -} - - -function testDynamicLink_parseDeepLink_linkWithinLink() { - var deepLink = - 'https://example.firebaseapp.com/__/auth/callback#oauthResponse'; - var linkWithLink = 'https://example.firebaseapp.com/__/auth/callback?link=' + - encodeURIComponent(deepLink); - var url = 'https://example.app.goo.gl/?link=' + - encodeURIComponent(linkWithLink); - assertEquals(deepLink, fireauth.DynamicLink.parseDeepLink(url)); -} - - -function testDynamicLink_parseDeepLink_customUrlSchemeDeepLink() { - var deepLink = - 'https://example.firebaseapp.com/__/auth/callback#oauthResponse'; - var url = 'comexampleiosurl://google/link?deep_link_id=' + - encodeURIComponent(deepLink); - assertEquals(deepLink, fireauth.DynamicLink.parseDeepLink(url)); -} - - -function testDynamicLink_parseDeepLink_reverseClientIdSchemeDeepLink() { - var deepLink = - 'https://example.firebaseapp.com/__/auth/callback#oauthResponse'; - var url = 'com.googleusercontent.apps.123456://firebaseauth/link?' + - 'deep_link_id=' + encodeURIComponent(deepLink); - assertEquals(deepLink, fireauth.DynamicLink.parseDeepLink(url)); -} - - -function testDynamicLink_parseDeepLink_customUrlSchemeLinkWithinLink() { - var deepLink = - 'https://example.firebaseapp.com/__/auth/callback#oauthResponse'; - var linkWithLink = 'https://example.firebaseapp.com/__/auth/callback?link=' + - encodeURIComponent(deepLink); - var url = 'comexampleiosurl://google/link?deep_link_id=' + - encodeURIComponent(linkWithLink); - assertEquals(deepLink, fireauth.DynamicLink.parseDeepLink(url)); -} - - -function testDynamicLink_parseDeepLink_clientIdCustomUrlSchemeLinkWithinLink() { - var deepLink = - 'https://example.firebaseapp.com/__/auth/callback#oauthResponse'; - var linkWithLink = 'https://example.firebaseapp.com/__/auth/callback?link=' + - encodeURIComponent(deepLink); - var url = 'com.googleusercontent.apps.123456://firebaseauth/link?' + - 'deep_link_id=' + encodeURIComponent(linkWithLink); - assertEquals(deepLink, fireauth.DynamicLink.parseDeepLink(url)); -} diff --git a/packages/auth/test/error_test.js b/packages/auth/test/error_test.js deleted file mode 100644 index d3d659d2496..00000000000 --- a/packages/auth/test/error_test.js +++ /dev/null @@ -1,671 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for error_app.js and error_auth.js. - */ - -goog.provide('fireauth.errorTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthErrorWithCredential'); -goog.require('fireauth.AuthProvider'); -goog.require('fireauth.FacebookAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.InvalidOriginError'); -goog.require('fireauth.authenum.Error'); -goog.require('goog.object'); -goog.require('goog.string.format'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.errorTest'); - - -var now = new Date(); - - -function testAuthError() { - var error = new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - assertEquals('auth/internal-error', error['code']); - assertEquals('An internal error has occurred.', error['message']); - // Test toJSON(). - assertObjectEquals({ - code: error['code'], - message: error['message'] - }, error.toJSON()); - // Make sure JSON.stringify works and uses underlying toJSON. - assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); -} - - -function testAuthError_serverResponse_defaultMessage() { - var serverResponse = { - 'mfaInfo': { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }, - 'mfaPendingCredential': 'PENDING_CREDENTIAL', - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + - '"name":{"givenName":"John","familyName":"Doe"}}' - }; - var error = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, null, serverResponse); - - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals( - fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.MFA_REQUIRED], - error['message']); - assertObjectEquals(serverResponse, error.serverResponse); - // Test toJSON(). - assertObjectEquals({ - code: error['code'], - message: error['message'], - serverResponse: error.serverResponse - }, error.toJSON()); - // Make sure JSON.stringify works and uses underlying toJSON. - assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); - - // Confirm toPlainObject behavior. - assertObjectEquals(error.toJSON(), error.toPlainObject()); - - // Confirm fromPlainObject behavior. - assertObjectEquals( - error, - fireauth.AuthError.fromPlainObject(error.toPlainObject())); -} - - -function testAuthError_serverResponse_customMessage() { - var serverResponse = { - 'mfaInfo': { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }, - 'mfaPendingCredential': 'PENDING_CREDENTIAL', - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + - '"name":{"givenName":"John","familyName":"Doe"}}' - }; - var error = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - 'custom message', - serverResponse); - - assertEquals('auth/multi-factor-auth-required', error['code']); - assertEquals('custom message', error['message']); - assertObjectEquals(serverResponse, error.serverResponse); - // Test toJSON(). - assertObjectEquals({ - code: error['code'], - message: error['message'], - serverResponse: error.serverResponse - }, error.toJSON()); - // Make sure JSON.stringify works and uses underlying toJSON. - assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); - - // Confirm toPlainObject behavior. - assertObjectEquals(error.toJSON(), error.toPlainObject()); - - // Confirm fromPlainObject behavior. - assertObjectEquals( - error, - fireauth.AuthError.fromPlainObject(error.toPlainObject())); -} - - -function testAuthError_errorTranslation_match() { - var error = new fireauth.AuthError(fireauth.authenum.Error.USER_DELETED); - // Translate USER_DELETED to USER_MISMATCH. - var translatedError = fireauth.AuthError.translateError( - error, - fireauth.authenum.Error.USER_DELETED, - fireauth.authenum.Error.USER_MISMATCH); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.USER_MISMATCH); - // Expected new error should be returned. - assertEquals( - JSON.stringify(expectedError), JSON.stringify(translatedError.toJSON())); -} - - -function testAuthError_errorTranslation_mismatch() { - var error = new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR); - // Translate USER_DELETED to USER_MISMATCH. - var translatedError = fireauth.AuthError.translateError( - error, - fireauth.authenum.Error.USER_DELETED, - fireauth.authenum.Error.USER_MISMATCH); - // Same error should be returned. - assertEquals(error, translatedError); -} - - -function testAuthErrorWithCredential() { - var credential = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN'); - var error = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential - }, - 'Account already exists, please confirm and link.'); - - assertEquals('user@example.com', error['email']); - assertUndefined(error['phoneNumber']); - assertEquals('auth/account-exists-with-different-credential', error['code']); - assertEquals(credential, error['credential']); - assertEquals( - 'Account already exists, please confirm and link.', error['message']); - // Test toJSON(). - assertObjectEquals( - { - code: error['code'], - message: error['message'], - email: 'user@example.com', - providerId: 'facebook.com', - oauthAccessToken: 'ACCESS_TOKEN', - signInMethod: fireauth.FacebookAuthProvider['FACEBOOK_SIGN_IN_METHOD'] - }, - error.toJSON()); - assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); -} - - -function testInvalidOriginError() { - // HTTP origin. - var error = new fireauth.InvalidOriginError('http://www.example.com'); - assertEquals('auth/unauthorized-domain', error['code']); - assertEquals( - 'This domain (www.example.com) is not authorized to run this operation' + - '. Add it to the OAuth redirect domains list in the Firebase console -' + - '> Auth section -> Sign in method tab.', - error['message']); - // File origin. - var error2 = new fireauth.InvalidOriginError('file://path/index.html'); - assertEquals( - 'auth/operation-not-supported-in-this-environment', error2['code']); - assertEquals( - 'This operation is not supported in the environment this application i' + - 's running on. "location.protocol" must be http, https or chrome-exten' + - 'sion and web storage must be enabled.', - error2['message']); - // Test toJSON(). - assertObjectEquals({ - code: error['code'], - message: error['message'] - }, error.toJSON()); - assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); - // Chrome extension origin. - error3 = new fireauth.InvalidOriginError('chrome-extension://1234567890'); - assertEquals('auth/unauthorized-domain', error3['code']); - assertEquals( - 'This chrome extension ID (chrome-extension://1234567890) is not autho' + - 'rized to run this operation. Add it to the OAuth redirect domains lis' + - 't in the Firebase console -> Auth section -> Sign in method tab.', - error3['message']); -} - - -/** - * The allowed characters in the error code, as per style guide. - */ -var ERROR_CODE_FORMAT = /^[a-z\-\/]+$/; - - -/** - * Asserts that the error codes conform to the Firebase JS error format. - * @param {!Object} codes - */ -function assertErrorCodesHaveCorrectFormat(codes) { - goog.object.forEach(codes, function(code) { - var assertMessage = goog.string.format('Error code %s should only ' + - 'contain lower-case ASCII characters, forward slashes, and hyphens.', - code); - assertTrue(assertMessage, ERROR_CODE_FORMAT.test(code)); - }); -} - - -function testAuthErrorCodeFormat() { - assertErrorCodesHaveCorrectFormat(fireauth.authenum.Error); -} - - -function testAuthError_toPlainObject() { - var authError = new fireauth.AuthError('error1', 'message1'); - var authErrorObject = { - 'code': 'auth/error1', - 'message': 'message1' - }; - assertObjectEquals( - authErrorObject, - authError.toPlainObject()); -} - - -function testInvalidOriginError_toPlainObject() { - var invalidOriginError = new fireauth.InvalidOriginError( - 'http://www.example.com'); - var invalidOriginErrorObject = { - 'code': 'auth/unauthorized-domain', - 'message': invalidOriginError['message'] - }; - assertObjectEquals( - invalidOriginErrorObject, - invalidOriginError.toPlainObject()); -} - - -function testAuthErrorWithCredential_toPlainObject() { - var credential = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN'); - var error = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential - }, - 'Account already exists, please confirm and link.'); - var errorObject = { - 'code': 'auth/account-exists-with-different-credential', - 'email': 'user@example.com', - 'message': 'Account already exists, please confirm and link.', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'signInMethod': fireauth.FacebookAuthProvider['FACEBOOK_SIGN_IN_METHOD'] - }; - assertObjectEquals( - errorObject, - error.toPlainObject()); - - // Test with no credential and default message to be used. - var error2 = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com' - }, - null); - var errorObject2 = { - 'code': 'auth/account-exists-with-different-credential', - 'email': 'user@example.com', - 'message': 'An account already exists with the same email address but ' + - 'different sign-in credentials. Sign in using a provider associated wi' + - 'th this email address.' - }; - assertObjectEquals( - errorObject2, - error2.toPlainObject()); - - // Credential with ID Token. - var credential3 = fireauth.GoogleAuthProvider.credential('ID_TOKEN'); - var error3 = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.EMAIL_EXISTS, - { - email: 'user@example.com', - credential: credential3 - }, - 'The email address is already in use by another account.'); - var errorObject3 = { - 'code': 'auth/email-already-in-use', - 'email': 'user@example.com', - 'message': 'The email address is already in use by another account.', - 'providerId': 'google.com', - 'oauthIdToken': 'ID_TOKEN', - 'signInMethod': fireauth.GoogleAuthProvider['GOOGLE_SIGN_IN_METHOD'] - }; - assertObjectEquals( - errorObject3, - error3.toPlainObject()); - - // AuthErrorWithCredential with just a credential and no email or phoneNumber. - var credential4 = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN'); - var error4 = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - credential: credential4 - }, - 'This credential is already associated with a different user account.'); - var errorObject4 = { - 'code': 'auth/credential-already-in-use', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'signInMethod': fireauth.FacebookAuthProvider['FACEBOOK_SIGN_IN_METHOD'] - }; - assertObjectEquals( - errorObject4, - error4.toPlainObject()); - - // AuthErrorWithCredential with credential, email and tenant ID. - var credential5 = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN'); - var error5 = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential5, - tenantId: 'TENANT_ID' - }, - 'This credential is already associated with a different user account.'); - var errorObject5 = { - 'code': 'auth/credential-already-in-use', - 'email': 'user@example.com', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'signInMethod': fireauth.FacebookAuthProvider['FACEBOOK_SIGN_IN_METHOD'], - 'tenantId': 'TENANT_ID' - }; - assertObjectEquals( - errorObject5, - error5.toPlainObject()); -} - - -function testAuthErrorWithCredential_fromPlainObject() { - var credential = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN'); - var error = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential - }, - 'Account already exists, please confirm and link.'); - var errorObject = { - 'code': 'auth/account-exists-with-different-credential', - 'email': 'user@example.com', - 'message': 'Account already exists, please confirm and link.', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN' - }; - var errorObject2 = { - 'email': 'user@example.com', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN' - }; - var internalError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - var needConfirmationError = new fireauth.AuthError( - fireauth.authenum.Error.NEED_CONFIRMATION); - // Empty response will return an invalid error. - assertNull(fireauth.AuthErrorWithCredential.fromPlainObject({})); - // Response with no error code is invalid. - assertNull(fireauth.AuthErrorWithCredential.fromPlainObject(errorObject2)); - // Regular error. - assertObjectEquals( - internalError, - fireauth.AuthErrorWithCredential.fromPlainObject( - {'code': 'auth/internal-error'})); - // Confirmation error with no credential or email will return a regular error. - assertObjectEquals( - needConfirmationError, - fireauth.AuthErrorWithCredential.fromPlainObject( - {'code': 'auth/account-exists-with-different-credential'})); - // Auth email credential error. - assertObjectEquals( - error, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject)); - - // Credential with ID token. - var credential3 = fireauth.GoogleAuthProvider.credential('ID_TOKEN'); - var error3 = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential3 - }, - 'This credential is already associated with a different user account.'); - var errorObject3 = { - 'code': 'auth/credential-already-in-use', - 'email': 'user@example.com', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'providerId': 'google.com', - 'oauthIdToken': 'ID_TOKEN' - }; - var errorObject3NoPrefix = { - 'code': 'credential-already-in-use', - 'email': 'user@example.com', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'providerId': 'google.com', - 'oauthIdToken': 'ID_TOKEN' - }; - assertObjectEquals( - error3, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject3)); - // If the error code prefix is missing. - assertObjectEquals( - error3, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject3NoPrefix)); - - // AuthErrorWithCredential with just a credential - var credential4 = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN'); - var error4 = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - credential: credential4 - }, - 'This credential is already associated with a different user account.'); - var errorObject4 = { - 'code': 'auth/credential-already-in-use', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN' - }; - var errorObject4NoPrefix = { - 'code': 'credential-already-in-use', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN' - }; - assertObjectEquals( - error4, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject4)); - // If the error code prefix is missing. - assertObjectEquals( - error4, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject4NoPrefix)); - - // AuthErrorWithCredential with credential, email and tenant ID. - var credential5 = fireauth.FacebookAuthProvider.credential('ACCESS_TOKEN'); - var error5 = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential5, - tenantId: 'TENANT_ID' - }, - 'This credential is already associated with a different user account.'); - var errorObject5 = { - 'code': 'auth/credential-already-in-use', - 'email': 'user@example.com', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'tenantId': 'TENANT_ID' - }; - var errorObject5NoPrefix = { - 'code': 'credential-already-in-use', - 'email': 'user@example.com', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'providerId': 'facebook.com', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'tenantId': 'TENANT_ID' - }; - assertObjectEquals( - error5, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject5)); - // If the error code prefix is missing. - assertObjectEquals( - error5, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject5NoPrefix)); -} - - -function testAuthErrorWithCredential_oauthCredential_idTokenNonce() { - var email = 'user@example.com'; - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - // Subset of server response. - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider', - // Injected by rpcHandler. - 'nonce': 'NONCE' - }); - var error = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: email, - credential: credential - }); - - assertEquals('auth/credential-already-in-use', error['code']); - assertEquals(email, error['email']); - assertEquals(credential, error['credential']); - - var errorObject = { - 'code': 'auth/credential-already-in-use', - 'email': email, - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider', - 'nonce': 'NONCE' - }; - assertObjectEquals( - error, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject)); -} - - -function testAuthErrorWithCredential_oauthCredential_pendingToken() { - var email = 'user@example.com'; - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - // Subset of server response. - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'providerId': 'oidc.provider' - }); - var error = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: email, - credential: credential - }); - - assertEquals('auth/credential-already-in-use', error['code']); - assertEquals(email, error['email']); - assertEquals(credential, error['credential']); - - var errorObject = { - 'code': 'auth/credential-already-in-use', - 'email': email, - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'providerId': 'oidc.provider' - }; - assertObjectEquals( - error, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject)); -} - - -function testAuthErrorWithCredential_phoneCredential() { - var temporaryProof = 'theTempProof'; - var phoneNumber = '+16505550101'; - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }); - var error = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - phoneNumber: phoneNumber, - credential: credential - }); - - assertEquals(phoneNumber, error['phoneNumber']); - assertEquals(credential, error['credential']); - assertUndefined(error['email']); -} - - -function testAuthErrorWithCredential_phoneCredential_fromPlainObject() { - var temporaryProof = 'theTempProof'; - var phoneNumber = '+16505550101'; - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }); - var error = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - phoneNumber: phoneNumber, - credential: credential - }); - var errorObject = { - 'code': 'auth/credential-already-in-use', - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }; - assertObjectEquals( - error, - fireauth.AuthErrorWithCredential.fromPlainObject(errorObject)); -} - - -function testAuthErrorWithCredential_phoneCredential_toPlainObject() { - var temporaryProof = 'theTempProof'; - var phoneNumber = '+16505550101'; - var credential = fireauth.AuthProvider.getCredentialFromResponse({ - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber - }); - var error = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - phoneNumber: phoneNumber, - credential: credential - }); - var errorObject = { - 'code': 'auth/credential-already-in-use', - 'message': 'This credential is already associated with a different user ' + - 'account.', - 'temporaryProof': temporaryProof, - 'phoneNumber': phoneNumber, - 'providerId': 'phone' - }; - assertObjectEquals( - errorObject, - error.toPlainObject()); -} - diff --git a/packages/auth/test/exports_lib_test.js b/packages/auth/test/exports_lib_test.js deleted file mode 100644 index 1d06d1d1bc0..00000000000 --- a/packages/auth/test/exports_lib_test.js +++ /dev/null @@ -1,276 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.exportlibTest'); - -goog.require('fireauth.args'); -goog.require('fireauth.exportlib'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.exportlibTest'); - -var Dog; -var obj; -var Provider; - -function setUp() { - Dog = function(name) { - this.name_ = name; - }; - - Dog.prototype.talk = function() { - return 'Woof'; - }; - - Dog.prototype.eat = function(food) { - return '*' + this.name_ + ' eats ' + food + '*'; - }; - - obj = { - myFunc: function(myBool) { - return 'I got the boolean ' + myBool; - } - }; - Provider = function() { - this.scopes_ = []; - }; - Provider.credential = function(cred) { - return { - 'cred': cred - }; - }; - Provider.prototype.addScope = function(scope) { - this.scopes_.push(scope); - }; - Provider.prototype.getScopes = function() { - return this.scopes_; - }; - Provider.PROVIDER_ID = 'providerId'; -} - - -function testWrapMethodWithArgumentVerifier_wrapperStaticAndProto_valid() { - fireauth.exportlib.wrapMethodWithArgumentVerifier_( - 'Provider', Provider, []); - fireauth.exportlib.wrapMethodWithArgumentVerifier_( - 'Provider.prototype.addScope', - Provider.prototype.addScope, [ - fireauth.args.string('scope') - ]); - fireauth.exportlib.wrapMethodWithArgumentVerifier_( - 'Provider.prototype.getScopes', - Provider.prototype.getScopes, []); - fireauth.exportlib.wrapMethodWithArgumentVerifier_( - 'Provider.credential', - Provider.credential, [ - fireauth.args.object() - ]); - var provider = new Provider(); - provider.addScope('s1'); - provider.addScope('s2'); - assertArrayEquals(['s1', 's2'], provider.getScopes()); - assertObjectEquals({'cred': 'something'}, Provider.credential('something')); - assertEquals('providerId', Provider.PROVIDER_ID); -} - - -function testWrapMethodWithArgumentVerifier_constructor_noArgs_valid() { - var ExportedDog = - fireauth.exportlib.wrapMethodWithArgumentVerifier_('Dog', Dog, - [fireauth.args.string('name')]); - new ExportedDog('Snoopy'); -} - - -function testWrapMethodWithArgumentVerifier_constructor_noArgs_invalid() { - var ExportedDog = - fireauth.exportlib.wrapMethodWithArgumentVerifier_('Dog', Dog, - [fireauth.args.string('name')]); - assertThrows(function() { - new ExportedDog(); - }); -} - - -function testWrapMethodWithArgumentVerifier_method_noArgs_valid() { - Dog.prototype.exportedTalk = - fireauth.exportlib.wrapMethodWithArgumentVerifier_('talk', - Dog.prototype.talk, []); - var snoopy = new Dog('Snoopy'); - assertEquals('Woof', snoopy.exportedTalk()); -} - - -function testWrapMethodWithArgumentVerifier_method_noArgs_invalid() { - Dog.prototype.exportedTalk = - fireauth.exportlib.wrapMethodWithArgumentVerifier_('talk', - Dog.prototype.talk, []); - var snoopy = new Dog('Snoopy'); - assertThrows(function() { - snoopy.exportedTalk('badArgument'); - }); -} - - -function testWrapMethodWithArgumentVerifier_method_oneArg_valid() { - Dog.prototype.exportedEat = - fireauth.exportlib.wrapMethodWithArgumentVerifier_('eat', - Dog.prototype.eat, - [fireauth.args.string('food')]); - var snoopy = new Dog('Snoopy'); - assertEquals('*Snoopy eats pizza*', snoopy.exportedEat('pizza')); -} - - -function testWrapMethodWithArgumentVerifier_method_oneArg_invalid() { - Dog.prototype.exportedEat = - fireauth.exportlib.wrapMethodWithArgumentVerifier_('eat', - Dog.prototype.eat, - [fireauth.args.string('food')]); - assertThrows(function() { - snoopy.exportedEat(13); - }); -} - - -function testWrapMethodWithArgumentVerifier_static_oneArg_valid() { - obj.exportedFunc = - fireauth.exportlib.wrapMethodWithArgumentVerifier_('myFunc', obj.myFunc, - [fireauth.args.bool('myBool')]); - assertEquals('I got the boolean true', obj.exportedFunc(true)); -} - - -function testWrapMethodWithArgumentVerifier_static_oneArg_invalid() { - obj.exportedFunc = - fireauth.exportlib.wrapMethodWithArgumentVerifier_('myFunc', obj.myFunc, - [fireauth.args.bool('myBool')]); - assertThrows(function() { - assertEquals('I got the boolean true', obj.exportedFunc('hello')); - }); -} - -function testExportPrototypeProperties() { - var obj = { - originalProp: 10, - originalProp2: 12 - }; - fireauth.exportlib.exportPrototypeProperties(obj, { - originalProp: { - name: 'newProp', - arg: fireauth.args.number('newProp') - }, - originalProp2: { - name: 'newProp2', - arg: fireauth.args.number('newProp2') - } - }); - - assertEquals(10, obj.originalProp); - assertEquals(10, obj['newProp']); - assertEquals(12, obj.originalProp2); - assertEquals(12, obj['newProp2']); - - // Changing the new property should update the old. - obj['newProp'] = 20; - obj['newProp2'] = 5; - assertEquals(20, obj.originalProp); - assertEquals(20, obj['newProp']); - assertEquals(5, obj.originalProp2); - assertEquals(5, obj['newProp2']); - - // Changing the old property should update the new. - obj.originalProp = 30; - obj.originalProp2 = 4; - assertEquals(30, obj.originalProp); - assertEquals(30, obj['newProp']); - assertEquals(4, obj.originalProp2); - assertEquals(4, obj['newProp2']); - - // Check argument validation. - assertThrows(function() { - obj['newProp'] = false; - }); - // Previous value should remain. - assertEquals(30, obj['newProp']); -} - -/** - * Tests that exportPrototypeProperties works when run on an object prototype. - */ -function testExportPrototypeProperties_prototype() { - var Obj = function() {}; - Obj.prototype.originalProp = 10; - fireauth.exportlib.exportPrototypeProperties(Obj.prototype, { - originalProp: { - name: 'newProp', - arg: fireauth.args.number('newProp') - } - }); - var obj = new Obj(); - - assertEquals(10, obj.originalProp); - assertEquals(10, obj['newProp']); - - // Changing the new property should update the old. - obj['newProp'] = 20; - assertEquals(20, obj.originalProp); - assertEquals(20, obj['newProp']); - - // Changing the old property should update the new. - obj.originalProp = 30; - assertEquals(30, obj.originalProp); - assertEquals(30, obj['newProp']); - - // Check argument validation. - assertThrows(function() { - obj['newProp'] = false; - }); - // Previous value should remain. - assertEquals(30, obj['newProp']); -} - - -/** - * Tests that exportPrototypeProperties works when you try to export a property - * without changing the symbol. - */ -function testExportPrototypeProperties_exportSelf() { - var Obj = function() { - this['propName'] = 10; - }; - fireauth.exportlib.exportPrototypeProperties(Obj.prototype, { - propName: { - name: 'propName', - arg: fireauth.args.number('newProp') - } - }); - var obj = new Obj(); - - assertEquals(10, obj['propName']); - - // We should still be able to change the property. - obj['propName'] = 20; - assertEquals(20, obj['propName']); - - // Check argument validation. This should not throw since the property name is - // the same. - assertNotThrows(function() { - obj['newProp'] = false; - }); - assertEquals(false, obj['newProp']); -} diff --git a/packages-exp/auth-exp/test/helpers/api/helper.ts b/packages/auth/test/helpers/api/helper.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/api/helper.ts rename to packages/auth/test/helpers/api/helper.ts diff --git a/packages-exp/auth-exp/test/helpers/delay.ts b/packages/auth/test/helpers/delay.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/delay.ts rename to packages/auth/test/helpers/delay.ts diff --git a/packages-exp/auth-exp/test/helpers/fake_service_worker.ts b/packages/auth/test/helpers/fake_service_worker.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/fake_service_worker.ts rename to packages/auth/test/helpers/fake_service_worker.ts diff --git a/packages-exp/auth-exp/test/helpers/id_token_response.ts b/packages/auth/test/helpers/id_token_response.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/id_token_response.ts rename to packages/auth/test/helpers/id_token_response.ts diff --git a/packages-exp/auth-exp/test/helpers/iframe_event.ts b/packages/auth/test/helpers/iframe_event.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/iframe_event.ts rename to packages/auth/test/helpers/iframe_event.ts diff --git a/packages-exp/auth-exp/test/helpers/integration/emulator_rest_helpers.ts b/packages/auth/test/helpers/integration/emulator_rest_helpers.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/integration/emulator_rest_helpers.ts rename to packages/auth/test/helpers/integration/emulator_rest_helpers.ts diff --git a/packages-exp/auth-exp/test/helpers/integration/helpers.ts b/packages/auth/test/helpers/integration/helpers.ts similarity index 97% rename from packages-exp/auth-exp/test/helpers/integration/helpers.ts rename to packages/auth/test/helpers/integration/helpers.ts index e84a22b491b..4f9518a6717 100644 --- a/packages-exp/auth-exp/test/helpers/integration/helpers.ts +++ b/packages/auth/test/helpers/integration/helpers.ts @@ -16,7 +16,7 @@ */ import * as sinon from 'sinon'; -import { deleteApp, initializeApp } from '@firebase/app-exp'; +import { deleteApp, initializeApp } from '@firebase/app'; import { Auth, User } from '../../../src/model/public_types'; import { getAuth, connectAuthEmulator } from '../../../'; // Use browser OR node dist entrypoint depending on test env. diff --git a/packages-exp/auth-exp/test/helpers/integration/settings.ts b/packages/auth/test/helpers/integration/settings.ts similarity index 97% rename from packages-exp/auth-exp/test/helpers/integration/settings.ts rename to packages/auth/test/helpers/integration/settings.ts index 0cbf665efc6..6a797e44a48 100644 --- a/packages-exp/auth-exp/test/helpers/integration/settings.ts +++ b/packages/auth/test/helpers/integration/settings.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseOptions } from '@firebase/app-exp'; +import { FirebaseOptions } from '@firebase/app'; // __karma__ is an untyped global // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages-exp/auth-exp/test/helpers/jwt.ts b/packages/auth/test/helpers/jwt.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/jwt.ts rename to packages/auth/test/helpers/jwt.ts diff --git a/packages-exp/auth-exp/test/helpers/mock_auth.ts b/packages/auth/test/helpers/mock_auth.ts similarity index 98% rename from packages-exp/auth-exp/test/helpers/mock_auth.ts rename to packages/auth/test/helpers/mock_auth.ts index 76c8aee1a49..459621cd77d 100644 --- a/packages-exp/auth-exp/test/helpers/mock_auth.ts +++ b/packages/auth/test/helpers/mock_auth.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { PopupRedirectResolver } from '../../src/model/public_types'; import { debugErrorMap } from '../../src'; diff --git a/packages-exp/auth-exp/test/helpers/mock_auth_credential.ts b/packages/auth/test/helpers/mock_auth_credential.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/mock_auth_credential.ts rename to packages/auth/test/helpers/mock_auth_credential.ts diff --git a/packages-exp/auth-exp/test/helpers/mock_fetch.test.ts b/packages/auth/test/helpers/mock_fetch.test.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/mock_fetch.test.ts rename to packages/auth/test/helpers/mock_fetch.test.ts diff --git a/packages-exp/auth-exp/test/helpers/mock_fetch.ts b/packages/auth/test/helpers/mock_fetch.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/mock_fetch.ts rename to packages/auth/test/helpers/mock_fetch.ts diff --git a/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts b/packages/auth/test/helpers/mock_popup_redirect_resolver.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts rename to packages/auth/test/helpers/mock_popup_redirect_resolver.ts diff --git a/packages-exp/auth-exp/test/helpers/redirect_persistence.ts b/packages/auth/test/helpers/redirect_persistence.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/redirect_persistence.ts rename to packages/auth/test/helpers/redirect_persistence.ts diff --git a/packages-exp/auth-exp/test/helpers/timeout_stub.ts b/packages/auth/test/helpers/timeout_stub.ts similarity index 100% rename from packages-exp/auth-exp/test/helpers/timeout_stub.ts rename to packages/auth/test/helpers/timeout_stub.ts diff --git a/packages/auth/test/idp_test.js b/packages/auth/test/idp_test.js deleted file mode 100644 index d8830351200..00000000000 --- a/packages/auth/test/idp_test.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for idp.js - */ - -goog.provide('fireauth.idpTest'); - -goog.require('fireauth.idp'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.idpTest'); - - -function testGetIdpSetting_unknown() { - var settings = fireauth.idp.getIdpSettings('unknown'); - assertNull(settings); - assertArrayEquals( - [], - fireauth.idp.getReservedOAuthParams('unknown')); - settings = fireauth.idp.getIdpSettings('password'); - assertArrayEquals( - [], - fireauth.idp.getReservedOAuthParams('password')); - assertNull(settings); - assertArrayEquals( - [], - fireauth.idp.getReservedOAuthParams('anonymous')); -} - - -function testGetIdpSetting_google() { - var settings = fireauth.idp.getIdpSettings('google.com'); - assertObjectEquals(fireauth.idp.Settings.GOOGLE, settings); - assertArrayEquals( - ['client_id', 'response_type', 'scope', 'redirect_uri', 'state'], - fireauth.idp.getReservedOAuthParams('google.com')); -} - - -function testGetIdpSetting_facebook() { - var settings = fireauth.idp.getIdpSettings('facebook.com'); - assertObjectEquals(fireauth.idp.Settings.FACEBOOK, settings); - assertArrayEquals( - ['client_id', 'response_type', 'scope', 'redirect_uri', 'state'], - fireauth.idp.getReservedOAuthParams('facebook.com')); -} - - -function testGetIdpSetting_github() { - var settings = fireauth.idp.getIdpSettings('github.com'); - assertObjectEquals(fireauth.idp.Settings.GITHUB, settings); - assertArrayEquals( - ['client_id', 'response_type', 'scope', 'redirect_uri', 'state'], - fireauth.idp.getReservedOAuthParams('github.com')); -} - - -function testGetIdpSetting_twitter() { - var settings = fireauth.idp.getIdpSettings('twitter.com'); - assertObjectEquals(fireauth.idp.Settings.TWITTER, settings); - assertArrayEquals( - ['oauth_consumer_key', 'oauth_nonce', 'oauth_signature', - 'oauth_signature_method', 'oauth_timestamp', 'oauth_token', - 'oauth_version'], - fireauth.idp.getReservedOAuthParams('twitter.com')); -} - - -function testIsSaml() { - assertTrue(fireauth.idp.isSaml('saml.provider')); - assertTrue(fireauth.idp.isSaml('saml.')); - assertFalse(fireauth.idp.isSaml('saMl.provider')); - assertFalse(fireauth.idp.isSaml(null)); - assertFalse(fireauth.idp.isSaml(undefined)); - for (var key in fireauth.idp.ProviderId) { - assertFalse(fireauth.idp.isSaml(fireauth.idp.ProviderId[key])); - } - assertFalse(fireauth.idp.isSaml('generic.com')); - assertFalse(fireauth.idp.isSaml('asaml.b')); -} diff --git a/packages/auth/test/idtoken_test.js b/packages/auth/test/idtoken_test.js deleted file mode 100644 index ec63dd8e680..00000000000 --- a/packages/auth/test/idtoken_test.js +++ /dev/null @@ -1,463 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for idtoken.js - */ - -goog.provide('fireauth.IdTokenTest'); - -goog.require('fireauth.IdToken'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.IdTokenTest'); - -const stubs = new goog.testing.PropertyReplacer(); -const now = Date.now(); - -function setUp() { - stubs.replace(Date, 'now', function() { - return now; - }); -} - -function tearDown() { - stubs.reset(); -} - -// exp: 1326439044 -// sub: "679" -// aud: "204241631686" -// provider_id: "gmail.com" -// email: "test123456@gmail.com" -// federated_id: "https://www.google.com/accounts/123456789" -var tokenGmail = 'HEADER.ew0KICAiaXNzIjogIkdJVGtpdCIsDQogICJleHAiOiAxMzI2NDM5' + - 'MDQ0LA0KICAic3ViIjogIjY3OSIsDQogICJhdWQiOiAiMjA0MjQxNjMxNjg2IiwNCiAgImZl' + - 'ZGVyYXRlZF9pZCI6ICJodHRwczovL3d3dy5nb29nbGUuY29tL2FjY291bnRzLzEyMzQ1Njc4' + - 'OSIsDQogICJwcm92aWRlcl9pZCI6ICJnbWFpbC5jb20iLA0KICAiZW1haWwiOiAidGVzdDEy' + - 'MzQ1NkBnbWFpbC5jb20iDQp9.SIGNATURE'; - - -// exp: 1326446190 -// sub: "274" -// aud: "204241631686" -// provider_id: "yahoo.com" -// email: "user123@yahoo.com" -// federated_id: "https://me.yahoo.com/whoamiwhowhowho#4a4ac" -var tokenYahoo = 'HEADER.ew0KICAiaXNzIjogIkdJVGtpdCIsDQogICJleHAiOiAxMzI2NDQ2' + - 'MTkwLA0KICAic3ViIjogIjI3NCIsDQogICJhdWQiOiAiMjA0MjQxNjMxNjg2IiwNCiAgImZl' + - 'ZGVyYXRlZF9pZCI6ICJodHRwczovL21lLnlhaG9vLmNvbS93aG9hbWl3aG93aG93aG8jNGE0' + - 'YWMiLA0KICAicHJvdmlkZXJfaWQiOiAieWFob28uY29tIiwNCiAgImVtYWlsIjogInVzZXIx' + - 'MjNAeWFob28uY29tIg0KfQ==.SIGNATURE'; - - -// iss: "https://identitytoolkit.google.com/" -// aud: "12345678.apps.googleusercontent.com" -// iat: 1441246088 -// exp: 2442455688 -// sub: "1458474" -// email: "testuser@gmail.com" -// provider_id: "google.com" -// verified: true -// display_name: "John Doe" -// photo_url: "https://lh5.googleusercontent.com/1458474/photo.jpg" -var tokenGoogleWithFederatedId = 'HEADER.ew0KICAiaXNzIjogImh0dHBzOi8vaWRlbnRp' + - 'dHl0b29sa2l0Lmdvb2dsZS5jb20vIiwNCiAgImF1ZCI6ICIxMjM0NTY3OC5hcHBzLmdvb2ds' + - 'ZXVzZXJjb250ZW50LmNvbSIsDQogICJpYXQiOiAxNDQxMjQ2MDg4LA0KICAiZXhwIjogMjQ0' + - 'MjQ1NTY4OCwNCiAgInN1YiI6ICIxNDU4NDc0IiwNCiAgImVtYWlsIjogInRlc3R1c2VyQGdt' + - 'YWlsLmNvbSIsDQogICJwcm92aWRlcl9pZCI6ICJnb29nbGUuY29tIiwNCiAgInZlcmlmaWVk' + - 'IjogdHJ1ZSwNCiAgImRpc3BsYXlfbmFtZSI6ICJKb2huIERvZSIsDQogICJwaG90b191cmwi' + - 'OiAiaHR0cHM6Ly9saDUuZ29vZ2xldXNlcmNvbnRlbnQuY29tLzE0NTg0NzQvcGhvdG8uanBn' + - 'Ig0KfQ==.SIGNATURE'; - - -// exp: 1326446190 -// sub: "365" -// aud: "204241631686" -// is_anonymous: true -var tokenAnonymous = 'HEAD.eyJpc3MiOiJHSVRraXQiLCJleHAiOjEzMjY0NDYxOTAsInN1Yi' + - 'I6IjM2NSIsImF1ZCI6IjIwNDI0MTYzMTY4NiIsImlzX2Fub255bW91cyI6dHJ1ZX0' + - '.SIGNATURE'; - - -// iss: "https://securetoken.google.com/projectId" -// aud: "projectId" -// auth_time: 1506050282 -// user_id: "123456" -// sub: "123456" -// iat: 1506050283 -// exp: 1506053883 -// email: "user@example.com" -// email_verified: false -// phone_number: "+11234567890" -// firebase: {identities: {phone: ["+11234567890"], -// email: ["user@example.com"] -// }, sign_in_provider: "phone"} -var tokenPhone = 'HEAD.ew0KICAiaXNzIjogImh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLm' + - 'NvbS9wcm9qZWN0SWQiLA0KICAiYXVkIjogInByb2plY3RJZCIsDQogICJhdXRoX3RpbWUiOi' + - 'AxNTA2MDUwMjgyLA0KICAidXNlcl9pZCI6ICIxMjM0NTYiLA0KICAic3ViIjogIjEyMzQ1Ni' + - 'IsDQogICJpYXQiOiAxNTA2MDUwMjgzLA0KICAiZXhwIjogMTUwNjA1Mzg4MywNCiAgImVtYW' + - 'lsIjogInVzZXJAZXhhbXBsZS5jb20iLA0KICAiZW1haWxfdmVyaWZpZWQiOiBmYWxzZSwNCi' + - 'AgInBob25lX251bWJlciI6ICIrMTEyMzQ1Njc4OTAiLA0KICAiZmlyZWJhc2UiOiB7DQogIC' + - 'AgImlkZW50aXRpZXMiOiB7DQogICAgICAicGhvbmUiOiBbDQogICAgICAgICIrMTEyMzQ1Nj' + - 'c4OTAiDQogICAgICBdLA0KICAgICAgImVtYWlsIjogWw0KICAgICAgICAidXNlckBleGFtcG' + - 'xlLmNvbSINCiAgICAgIF0NCiAgICB9LA0KICAgICJzaWduX2luX3Byb3ZpZGVyIjogInBob2' + - '5lIg0KICB9DQp9.SIGNATURE'; - - -// "iss": "https://securetoken.google.com/projectId", -// "name": "John Doe", -// "admin": true, -// "aud": "projectId", -// "auth_time": 1522715325, -// "sub": "nep2uwNCK4PqjvoKjb0InVJHlGi1", -// "iat": 1522776807, -// "exp": 1522780575, -// "email": "testuser@gmail.com", -// "email_verified": true, -// "firebase": { -// "identities": { -// "email": [ -// "testuser@gmail.com" -// ] -// }, -// "sign_in_provider": "password" -// } -var tokenCustomClaim = 'HEAD.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5j' + - 'b20vcHJvamVjdElkIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImF1ZCI6InBy' + - 'b2plY3RJZCIsImF1dGhfdGltZSI6MTUyMjcxNTMyNSwic3ViIjoibmVwMnV3TkNLNFBxanZv' + - 'S2piMEluVkpIbEdpMSIsImlhdCI6MTUyMjc3NjgwNywiZXhwIjoxNTIyNzgwNTc1LCJlbWFp' + - 'bCI6InRlc3R1c2VyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFz' + - 'ZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInRlc3R1c2VyQGdtYWlsLmNvbSJdfSwic2ln' + - 'bl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.SIGNATURE'; - - -// "iss": "https://securetoken.google.com/projectId", -// "name": "John Doe", -// "role": "Админ", // <---- Note non-ascii characters here -// "aud": "projectId", -// "auth_time": 1522715325, -// "sub": "nep2uwNCK4PqjvoKjb0InVJHlGi1", -// "iat": 1522776807, -// "exp": 1522780575, -// "email": "testuser@gmail.com", -// "email_verified": true, -// "firebase": { -// "identities": { -// "email": [ -// "testuser@gmail.com" -// ] -// }, -// "sign_in_provider": "custom" -// } -var tokenCustomClaimWithUnicodeChar = 'HEAD.eyJpc3MiOiJodHRwczovL3NlY3VyZXRv' + - 'a2VuLmdvb2dsZS5jb20vcHJvamVjdElkIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ItC' + - 'Q0LTQvNC40L0iLCJhdWQiOiJwcm9qZWN0SWQiLCJhdXRoX3RpbWUiOjE1MjI3MTUzMjUsIn' + - 'N1YiI6Im5lcDJ1d05DSzRQcWp2b0tqYjBJblZKSGxHaTEiLCJpYXQiOjE1MjI3NzY4MDcsI' + - 'mV4cCI6MTUyMjc4MDU3NSwiZW1haWwiOiJ0ZXN0dXNlckBnbWFpbC5jb20iLCJlbWFpbF92' + - 'ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJ0ZXN' + - '0dXNlckBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJjdXN0b20ifX0=.SIGNA' + - 'TURE'; - - -// "iss": "https://securetoken.google.com/projectId", -// "name": "John Doe", -// "aud": "projectId", -// "auth_time": 1522715325, -// "sub": "nep2uwNCK4PqjvoKjb0InVJHlGi1", -// "iat": 1522776807, -// "exp": 1522780575, -// "email": "testuser@gmail.com", -// "email_verified": true, -// "firebase": { -// "identities": { -// "email": [ -// "testuser@gmail.com" -// ] -// }, -// "sign_in_provider": "password", -// "tenant": "1234567890123" -// } -var tokenMultiTenant = 'HEAD.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5j' + - 'b20vcHJvamVjdElkIiwibmFtZSI6IkpvaG4gRG9lIiwiYXVkIjoicHJvamVjdElkIiwiYXV0' + - 'aF90aW1lIjoxNTIyNzE1MzI1LCJzdWIiOiJuZXAydXdOQ0s0UHFqdm9LamIwSW5WSkhsR2kx' + - 'IiwiaWF0IjoxNTIyNzc2ODA3LCJleHAiOjE1MjI3ODA1NzUsImVtYWlsIjoidGVzdHVzZXJA' + - 'Z21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRp' + - 'ZXMiOnsiZW1haWwiOlsidGVzdHVzZXJAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVy' + - 'IjoicGFzc3dvcmQiLCJ0ZW5hbnQiOiIxMjM0NTY3ODkwMTIzIn19.SIGNATURE'; - - -/** - * Asserts the values in the token provided. - * @param {!fireauth.IdToken} token The ID token to assert. - * @param {?string} email The expected email. - * @param {number} exp The expected expiration field. - * @param {number} iat The token issuance time field. - * @param {?string} providerId The expected provider ID. - * @param {?string} displayName The expected display name. - * @param {?string} photoURL The expected photo URL. - * @param {boolean} anonymous The expected anonymous status. - * @param {string} localId The expected user ID. - * @param {?string} federatedId The expected federated ID. - * @param {boolean} verified The expected verified status. - * @param {?string} phoneNumber The expected phone number. - * @param {?string} tenantId The expected tenant ID. - */ -function assertToken( - token, - email, - exp, - iat, - providerId, - displayName, - photoURL, - anonymous, - localId, - federatedId, - verified, - phoneNumber, - tenantId) { - assertEquals(email, token.getEmail()); - assertEquals(exp, token.getExp()) - assertEquals(exp - iat, token.getExpiresIn());; - assertEquals(providerId, token.getProviderId()); - assertEquals(displayName, token.getDisplayName()); - assertEquals(photoURL, token.getPhotoUrl()); - assertEquals(localId, token.getLocalId()); - assertEquals(federatedId, token.getFederatedId()); - assertEquals(anonymous, token.isAnonymous()); - assertEquals(verified, token.isVerified()); - assertEquals(phoneNumber, token.getPhoneNumber()); - assertEquals(tenantId, token.getTenantId()); -} - - -function testParse_invalid() { - assertNull(fireauth.IdToken.parse('gegege.invalid.ggrgheh')); -} - - -function testParse_anonymous() { - const token = fireauth.IdToken.parse(tokenAnonymous); - assertToken( - token, - null, - 1326446190, - 1326446190, - null, - null, - null, - true, - '365', - null, - false, - null, - null); - assertEquals(tokenAnonymous, token.toString()); -} - - -function testParse_tenantId() { - const token = fireauth.IdToken.parse(tokenMultiTenant); - assertToken( - token, - 'testuser@gmail.com', - 1522780575, - 1522776807, - 'password', - null, - null, - false, - 'nep2uwNCK4PqjvoKjb0InVJHlGi1', - null, - false, - null, - '1234567890123'); -} - - -function testParse_needPadding() { - const token = fireauth.IdToken.parse(tokenGmail); - assertToken( - token, - 'test123456@gmail.com', - 1326439044, - 1326439044, - 'gmail.com', - null, - null, - false, - '679', - 'https://www.google.com/accounts/123456789', - false, - null, - null); - assertTrue(token.isExpired()); - assertEquals(tokenGmail, token.toString()); -} - - -function testParse_noPadding() { - const token = fireauth.IdToken.parse(tokenYahoo); - assertToken( - token, - 'user123@yahoo.com', - 1326446190, - 1326446190, - 'yahoo.com', - null, - null, - false, - '274', - 'https://me.yahoo.com/whoamiwhowhowho#4a4ac', - false, - null, - null); - assertTrue(token.isExpired()); - assertEquals(tokenYahoo, token.toString()); -} - - -function testParse_unexpired() { - // This token will expire in year 2047. - const token = fireauth.IdToken.parse(tokenGoogleWithFederatedId); - assertToken( - token, - 'testuser@gmail.com', - 2442455688, - 1441246088, - 'google.com', - 'John Doe', - 'https://lh5.googleusercontent.com/1458474/photo.jpg', - false, - '1458474', - null, - true, - null, - null); - // Check issuer of token. - assertEquals('https://identitytoolkit.google.com/', token.getIssuer()); - assertFalse(token.isExpired()); - assertEquals(tokenGoogleWithFederatedId, token.toString()); -} - - -function testParse_phoneAndFirebaseProviderId() { - const token = fireauth.IdToken.parse(tokenPhone); - assertToken( - token, - 'user@example.com', - 1506053883, - 1506050283, - 'phone', - null, - null, - false, - '123456', - null, - false, - '+11234567890', - null); - assertEquals('https://securetoken.google.com/projectId', token.getIssuer()); - assertEquals(tokenPhone, token.toString()); -} - - -function testParseIdTokenClaims_invalid() { - assertNull(fireauth.IdToken.parseIdTokenClaims('gegege.invalid.ggrgheh')); -} - - -function testParseIdTokenClaims_null() { - assertNull(fireauth.IdToken.parseIdTokenClaims(null)); -} - - -function testParseIdTokenClaims() { - const tokenJSON = fireauth.IdToken.parseIdTokenClaims( - tokenGoogleWithFederatedId); - assertObjectEquals( - { - 'iss': 'https://identitytoolkit.google.com/', - 'aud': '12345678.apps.googleusercontent.com', - 'iat': 1441246088, - 'exp': 2442455688, - 'sub': '1458474', - 'email': 'testuser@gmail.com', - 'provider_id': 'google.com', - 'verified': true, - 'display_name': 'John Doe', - 'photo_url': 'https://lh5.googleusercontent.com/1458474/photo.jpg' - }, - tokenJSON); -} - - -function testParseIdTokenClaims_customClaims() { - const tokenJSON = fireauth.IdToken.parseIdTokenClaims(tokenCustomClaim); - assertObjectEquals( - { - 'iss': 'https://securetoken.google.com/projectId', - 'name': 'John Doe', - 'admin': true, - 'aud': 'projectId', - 'auth_time': 1522715325, - 'sub': 'nep2uwNCK4PqjvoKjb0InVJHlGi1', - 'iat': 1522776807, - 'exp': 1522780575, - 'email': "testuser@gmail.com", - 'email_verified': true, - 'firebase': { - 'identities': { - 'email': [ - 'testuser@gmail.com' - ] - }, - 'sign_in_provider': 'password' - } - }, - tokenJSON); -} - - -function testParseIdTokenClaims_tokenCustomClaimWithUnicodeChar() { - const tokenJSON = fireauth.IdToken.parseIdTokenClaims( - tokenCustomClaimWithUnicodeChar); - assertObjectEquals( - { - 'iss': 'https://securetoken.google.com/projectId', - 'name': 'John Doe', - 'role': 'Админ', - 'aud': 'projectId', - 'auth_time': 1522715325, - 'sub': 'nep2uwNCK4PqjvoKjb0InVJHlGi1', - 'iat': 1522776807, - 'exp': 1522780575, - 'email': "testuser@gmail.com", - 'email_verified': true, - 'firebase': { - 'identities': { - 'email': [ - 'testuser@gmail.com' - ] - }, - 'sign_in_provider': 'custom' - } - }, - tokenJSON); -} diff --git a/packages/auth/test/idtokenresult_test.js b/packages/auth/test/idtokenresult_test.js deleted file mode 100644 index 3a1634e02eb..00000000000 --- a/packages/auth/test/idtokenresult_test.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Tests for idtoken.js - */ - -goog.provide('fireauth.IdTokenResultTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.IdTokenResult'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly(); - - -function testIdTokenResult() { - // "iss": "https://securetoken.google.com/projectId", - // "name": "John Doe", - // "admin": true, - // "aud": "projectId", - // "auth_time": 1522715325, - // "sub": "nep2uwNCK4PqjvoKjb0InVJHlGi1", - // "iat": 1522776807, - // "exp": 1522780575, - // "email": "testuser@gmail.com", - // "email_verified": true, - // "firebase": { - // "identities": { - // "email": [ - // "testuser@gmail.com" - // ] - // }, - // "sign_in_provider": "password" - var tokenString = 'HEADER.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb' + - '20vcHJvamVjdElkIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImF1ZCI6InB' + - 'yb2plY3RJZCIsImF1dGhfdGltZSI6MTUyMjcxNTMyNSwic3ViIjoibmVwMnV3TkNLNFBxa' + - 'nZvS2piMEluVkpIbEdpMSIsImlhdCI6MTUyMjc3NjgwNywiZXhwIjoxNTIyNzgwNTc1LCJ' + - 'lbWFpbCI6InRlc3R1c2VyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJma' + - 'XJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInRlc3R1c2VyQGdtYWlsLmNvbSJ' + - 'dfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.SIGNATURE'; - var idTokenResult = new fireauth.IdTokenResult(tokenString); - fireauth.common.testHelper.assertIdTokenResult( - idTokenResult, - tokenString, - 1522780575, - 1522715325, - 1522776807, - 'password', - null, - { - 'iss': 'https://securetoken.google.com/projectId', - 'name': 'John Doe', - 'admin': true, - 'aud': 'projectId', - 'auth_time': 1522715325, - 'sub': 'nep2uwNCK4PqjvoKjb0InVJHlGi1', - 'iat': 1522776807, - 'exp': 1522780575, - 'email': "testuser@gmail.com", - 'email_verified': true, - 'firebase': { - 'identities': { - 'email': [ - 'testuser@gmail.com' - ] - }, - 'sign_in_provider': 'password' - } - }); -} - - -function testIdTokenResult_mfa() { - // "iss": "https://securetoken.google.com/projectId", - // "name": "John Doe", - // "admin": true, - // "aud": "projectId", - // "auth_time": 1522715325, - // "sub": "nep2uwNCK4PqjvoKjb0InVJHlGi1", - // "iat": 1522776807, - // "exp": 1522780575, - // "email": "testuser@gmail.com", - // "email_verified": true, - // "firebase": { - // "identities": { - // "email": [ - // "testuser@gmail.com" - // ] - // }, - // "additional_factors": { - // "phone": [ - // "+16505551234", - // "+16505557890" - // ] - // }, - // "sign_in_provider": "password", - // "sign_in_second_factor": "phone" - var tokenString = 'HEADER.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb' + - '20vcHJvamVjdElkIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImF1ZCI6InB' + - 'yb2plY3RJZCIsImF1dGhfdGltZSI6MTUyMjcxNTMyNSwic3ViIjoibmVwMnV3TkNLNFBxa' + - 'nZvS2piMEluVkpIbEdpMSIsImlhdCI6MTUyMjc3NjgwNywiZXhwIjoxNTIyNzgwNTc1LCJ' + - 'lbWFpbCI6InRlc3R1c2VyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJma' + - 'XJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInRlc3R1c2VyQGdtYWlsLmNvbSJ' + - 'dfSwiYWRkaXRpb25hbF9mYWN0b3JzIjp7InBob25lIjogWyIrMTY1MDU1NTEyMzQiLCIrM' + - 'TY1MDU1NTc4OTAiXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCIsInNpZ25faW5' + - 'fc2Vjb25kX2ZhY3RvciI6ICJwaG9uZSJ9fQ.SIGNATURE'; - var idTokenResult = new fireauth.IdTokenResult(tokenString); - fireauth.common.testHelper.assertIdTokenResult( - idTokenResult, - tokenString, - 1522780575, - 1522715325, - 1522776807, - 'password', - 'phone', - { - 'iss': 'https://securetoken.google.com/projectId', - 'name': 'John Doe', - 'admin': true, - 'aud': 'projectId', - 'auth_time': 1522715325, - 'sub': 'nep2uwNCK4PqjvoKjb0InVJHlGi1', - 'iat': 1522776807, - 'exp': 1522780575, - 'email': "testuser@gmail.com", - 'email_verified': true, - 'firebase': { - 'identities': { - 'email': [ - 'testuser@gmail.com' - ] - }, - 'additional_factors': { - 'phone': [ - '+16505551234', - '+16505557890' - ] - }, - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }); -} - - -function testIdTokenResult_invalid() { - var tokenString = 'gegege.invalid.ggrgheh'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'An internal error occurred. The token obtained by Firebase appears ' + - 'to be malformed. Please retry the operation.'); - var error = assertThrows(function() { - new fireauth.IdTokenResult(tokenString); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); -} - - -function testIdTokenResult_null() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'An internal error occurred. The token obtained by Firebase appears ' + - 'to be malformed. Please retry the operation.'); - var error = assertThrows(function() { - new fireauth.IdTokenResult(null); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); -} - - -function testIdTokenResult_missingRequiredFields() { - // "iss": "https://securetoken.google.com/projectId", - // "name": "John Doe", - // "admin": true, - // "aud": "projectId", - // "sub": "nep2uwNCK4PqjvoKjb0InVJHlGi1", - // "email": "testuser@gmail.com", - // "email_verified": true, - // "firebase": { - // "identities": { - // "email": [ - // "testuser@gmail.com" - // ] - // }, - // "sign_in_provider": "password" - var tokenString = 'HEADER.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb' + - '20vcHJvamVjdElkIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImF1ZCI6InB' + - 'yb2plY3RJZCIsInN1YiI6Im5lcDJ1d05DSzRQcWp2b0tqYjBJblZKSGxHaTEiLCJlbWFpb' + - 'CI6InRlc3R1c2VyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmF' + - 'zZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInRlc3R1c2VyQGdtYWlsLmNvbSJdfSwic' + - '2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.SIGNATURE'; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'An internal error occurred. The token obtained by Firebase appears ' + - 'to be malformed. Please retry the operation.'); - var error = assertThrows(function() { - new fireauth.IdTokenResult(tokenString); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); -} diff --git a/packages/auth/test/iframeclient/ifchandler_test.js b/packages/auth/test/iframeclient/ifchandler_test.js deleted file mode 100644 index 0822fa70387..00000000000 --- a/packages/auth/test/iframeclient/ifchandler_test.js +++ /dev/null @@ -1,2042 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for ifchandler.js - */ - -goog.provide('fireauth.iframeclient.IfcHandlerTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.FacebookAuthProvider'); -goog.require('fireauth.FederatedProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.InvalidOriginError'); -goog.require('fireauth.OAuthProvider'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.TwitterAuthProvider'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.constants'); -goog.require('fireauth.iframeclient.IfcHandler'); -goog.require('fireauth.iframeclient.IframeUrlBuilder'); -goog.require('fireauth.iframeclient.IframeWrapper'); -goog.require('fireauth.iframeclient.OAuthUrlBuilder'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Uri'); -goog.require('goog.testing.AsyncTestCase'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.mockmatchers'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.iframeclient.IfcHandlerTest'); - - -var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); -var stubs = new goog.testing.PropertyReplacer(); -var ifcHandler; -var authDomain = 'subdomain.firebaseapp.com'; -var apiKey = 'apiKey1'; -var appName = 'appName1'; -var authEvent; -var eventsMap = {}; -var version = '3.0.0'; -var clock; -var mockControl; -var ignoreArgument; - -function setUp() { - eventsMap = {}; - stubs.replace( - fireauth.iframeclient.IframeWrapper.prototype, - 'open_', - function() { - return goog.Promise.resolve(); - }); - stubs.replace( - fireauth.iframeclient.IframeWrapper.prototype, 'onReady', - goog.testing.recordFunction(goog.Promise.resolve)); - stubs.replace( - fireauth.iframeclient.IframeWrapper.prototype, - 'registerEvent', - function(eventName, handler) { - eventsMap[eventName] = handler; - }); - stubs.replace( - fireauth.iframeclient.IframeWrapper.prototype, - 'sendMessage', - function(message) { - assertEquals('webStorageSupport', message['type']); - var response = [{'status': 'ACK', 'webStorageSupport': true}]; - return goog.Promise.resolve(response); - }); - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); -} - - -function tearDown() { - stubs.reset(); - ifcHandler = null; - goog.dispose(clock); - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } -} - - -/** - * Simulates the current Auth language/frameworks on the specified App instance. - * @param {string} appName The expected App instance identifier. - * @param {?string} languageCode The default Auth language. - * @param {?Array} frameworks The list of frameworks on the Auth - * instance. - */ -function simulateAuthService(appName, languageCode, frameworks) { - stubs.replace( - firebase, - 'app', - function(name) { - assertEquals(appName, name); - return { - auth: function() { - return { - getLanguageCode: function() { - return languageCode; - }, - getFramework: function() { - return frameworks || []; - } - }; - } - }; - }); -} - - -/** - * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be - * used as Internet Explorer adds the stack trace as a property of the object. - * @param {!fireauth.AuthError} expected - * @param {!fireauth.AuthError} actual - */ -function assertErrorEquals(expected, actual) { - assertObjectEquals(expected.toPlainObject(), actual.toPlainObject()); -} - - -function testIframeUrlBuilder() { - var builder = new fireauth.iframeclient.IframeUrlBuilder( - 'example.firebaseapp.com', 'API_KEY', 'MY_APP'); - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP', - builder.setVersion(null).toString()); - // Set version parameter. - builder.setVersion('3.4.0'); - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP&v=3.4.0', - builder.toString()); - // Modify version parameter. - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP&v=3.4.1', - builder.setVersion('3.4.1').toString()); - // Remove version parameter. - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP', - builder.setVersion(null).toString()); - // Set eid parameter. - builder.setEndpointId('p'); - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP&eid=p', - builder.toString()); - // Modify eid parameter. - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP&eid=s', - builder.setEndpointId('s').toString()); - // Remove eid parameter. - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP', - builder.setEndpointId(null).toString()); - // Set fw parameter. - builder.setFrameworks(['f1', 'f2']); - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP&fw=' + encodeURIComponent('f1,f2'), - builder.toString()); - // Modify fw parameter. - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP&fw=f3', - builder.setFrameworks(['f3']).toString()); - // Remove fw parameter. - assertEquals( - 'https://example.firebaseapp.com/__/auth/iframe?apiKey=API_KEY&appName' + - '=MY_APP', - builder.setFrameworks(null).toString()); -} - - -/** - * Asserts IframeUrlBuilder.prototype.toString handles emulator URLs. - */ -function testIframeUrlBuilder_withEmulator() { - var builder = new fireauth.iframeclient.IframeUrlBuilder( - 'example.firebaseapp.com', 'API_KEY', 'MY_APP', { - url: 'http://emulator.test.domain:1234' - }); - assertEquals( - 'http://emulator.test.domain:1234/emulator/auth/iframe?' + - 'apiKey=API_KEY&appName=MY_APP', - builder.setVersion(null).toString()); -} - - -function testOAuthUrlBuilder() { - var redirectUrl = 'http://www.example.com/redirect?a=b#c'; - var redirectUrl2 = 'http://www.example.com/redirect2?d=e#f'; - var provider = new fireauth.GoogleAuthProvider(); - var additionalParams = { - // This entry should be ignored. - 'apiKey': 'OTHER_KEY', - 'apn': 'com.example.android', - 'sessionId': 'SESSION_ID1' - }; - var additionalParams2 = { - 'ibi': 'com.example.ios', - 'sessionId': 'SESSION_ID2' - }; - provider.addScope('scope1'); - provider.addScope('scope2'); - var customParams = { - 'hd': 'example.com', - 'login_hint': 'user@example.com', - 'state': 'bla' - }; - var filteredCustomParams = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - provider.setCustomParameters(customParams); - var builder = new fireauth.iframeclient.OAuthUrlBuilder( - 'example.firebaseapp.com', 'API_KEY', 'APP_NAME', 'signInWithPopup', - provider); - var partialUrl = 'https://example.firebaseapp.com/__/auth/handler?apiKey=A' + - 'PI_KEY&appName=APP_NAME&authType=signInWithPopup&providerId=google.co' + - 'm&customParameters=' + - encodeURIComponent(fireauth.util.stringifyJSON(filteredCustomParams)) + - '&scopes=' + encodeURIComponent('profile,scope1,scope2'); - assertEquals(partialUrl, builder.toString()); - // Add redirect URL. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl), - builder.setRedirectUrl(redirectUrl).toString()); - // Modify redirect URL. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2), - builder.setRedirectUrl(redirectUrl2).toString()); - // Add eventId. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID1', - builder.setEventId('EVENT_ID1').toString()); - // Modify eventId. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2', - builder.setEventId('EVENT_ID2').toString()); - // Add version. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2&v=3.4.0', - builder.setVersion('3.4.0').toString()); - // Modify version. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2&v=3.4.1', - builder.setVersion('3.4.1').toString()); - // Add additional parameters. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2&v=3.4.1&apn=' + - encodeURIComponent('com.example.android') + '&sessionId=SESSION_ID1', - builder.setAdditionalParameters(additionalParams).toString()); - // Modify additional parameters. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2&v=3.4.1&ibi=' + - encodeURIComponent('com.example.ios') + '&sessionId=SESSION_ID2', - builder.setAdditionalParameters(additionalParams2).toString()); - // Add tenantId. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2&v=3.4.1&ibi=' + - encodeURIComponent('com.example.ios') + - '&sessionId=SESSION_ID2&tid=TENANT_ID1', - builder.setTenantId('TENANT_ID1').toString()); - // Modify tenantId. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2&v=3.4.1&ibi=' + - encodeURIComponent('com.example.ios') + - '&sessionId=SESSION_ID2&tid=TENANT_ID2', - builder.setTenantId('TENANT_ID2').toString()); - // Delete tenantId. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2&v=3.4.1&ibi=' + - encodeURIComponent('com.example.ios') + '&sessionId=SESSION_ID2', - builder.setTenantId(null).toString()); - // Delete additional parameters. - assertEquals( - partialUrl + '&redirectUrl=' + encodeURIComponent(redirectUrl2) + - '&eventId=EVENT_ID2&v=3.4.1', - builder.setAdditionalParameters(null).toString()); - // Delete redirect URL. - assertEquals( - partialUrl + '&eventId=EVENT_ID2&v=3.4.1', - builder.setRedirectUrl(null).toString()); - // Delete eventId. - assertEquals( - partialUrl + '&v=3.4.1', - builder.setEventId(null).toString()); - // Delete version. - assertEquals(partialUrl, builder.setVersion(null).toString()); - // Set eid parameter. - builder.setEndpointId('p'); - assertEquals( - partialUrl + '&eid=p', - builder.toString()); - // Modify eid parameter. - assertEquals( - partialUrl + '&eid=s', - builder.setEndpointId('s').toString()); - // Remove eid parameter. - assertEquals( - partialUrl, - builder.setEndpointId(null).toString()); - - // Simulate frameworks logged. - var frameworks = ['firebaseui', 'angularfire']; - simulateAuthService('APP_NAME', null, frameworks); - assertEquals( - partialUrl + '&fw=' + encodeURIComponent(frameworks.join(',')), - builder.toString()); - // Remove frameworks. - simulateAuthService('APP_NAME', null, []); - assertEquals(partialUrl, builder.toString()); -} - - -function testOAuthUrlBuilder_localization() { - var expectedUrl; - var provider = new fireauth.GoogleAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - // Custom parameters with no language field as provided by the developer. - var customParams = { - 'hd': 'example.com', - 'login_hint': 'user@example.com', - 'state': 'bla' - }; - // Custom parameters with a language field as provided by the developer. - var customParamsWithLang = { - 'hd': 'example.com', - 'login_hint': 'user@example.com', - 'state': 'bla', - 'hl': 'de' - }; - // Expected filtered custom parameters with the default language added. - var expectedCustomParams = { - 'hd': 'example.com', - 'login_hint': 'user@example.com', - // Default language set. - 'hl': 'fr' - }; - // Expected filtered custom parameters with no language added. - var expectedCustomParamsWithoutLang = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - // Expected filtered custom parameters with non-default language added. - var expectedCustomParamsWithLang = { - 'hd': 'example.com', - 'login_hint': 'user@example.com', - // Default language overridden. - 'hl': 'de' - }; - // OAuth URL builder. - var builder = new fireauth.iframeclient.OAuthUrlBuilder( - 'example.firebaseapp.com', 'API_KEY', 'APP_NAME', 'signInWithPopup', - provider); - - // Simulate Auth language set. - simulateAuthService('APP_NAME', 'fr'); - // Pass custom parameters with no language field. - provider.setCustomParameters(customParams); - // Expected URL should include the default language. - expectedUrl = 'https://example.firebaseapp.com/__/auth/handler?apiKey=A' + - 'PI_KEY&appName=APP_NAME&authType=signInWithPopup&providerId=google.co' + - 'm&customParameters=' + - encodeURIComponent(fireauth.util.stringifyJSON(expectedCustomParams)) + - '&scopes=' + encodeURIComponent('profile,scope1,scope2'); - assertEquals(expectedUrl, builder.toString()); - - // Modify custom parameters to include a language parameter. - provider.setCustomParameters(customParamsWithLang); - // Expected URL should include the non-default language. - expectedUrl = 'https://example.firebaseapp.com/__/auth/handler?apiKey=A' + - 'PI_KEY&appName=APP_NAME&authType=signInWithPopup&providerId=google.co' + - 'm&customParameters=' + - encodeURIComponent(fireauth.util.stringifyJSON( - expectedCustomParamsWithLang)) + - '&scopes=' + encodeURIComponent('profile,scope1,scope2'); - assertEquals(expectedUrl, builder.toString()); - - // Simulate no default language. - simulateAuthService('APP_NAME', null); - // Set custom parameters without a language field. - provider.setCustomParameters(customParams); - expectedUrl = 'https://example.firebaseapp.com/__/auth/handler?apiKey=A' + - 'PI_KEY&appName=APP_NAME&authType=signInWithPopup&providerId=google.co' + - 'm&customParameters=' + - encodeURIComponent(fireauth.util.stringifyJSON( - expectedCustomParamsWithoutLang)) + - '&scopes=' + encodeURIComponent('profile,scope1,scope2'); - assertEquals(expectedUrl, builder.toString()); -} - - -function testOAuthUrlBuilder_genericIdp() { - var provider = new fireauth.OAuthProvider('idp.com'); - provider.addScope('thescope'); - var customParams = { - 'hl': 'es' - }; - provider.setCustomParameters(customParams); - var builder = new fireauth.iframeclient.OAuthUrlBuilder( - 'example.firebaseapp.com', 'API_KEY', 'APP_NAME', 'signInWithPopup', - provider); - var url = 'https://example.firebaseapp.com/__/auth/handler?' + - 'apiKey=API_KEY&appName=APP_NAME&authType=signInWithPopup&' + - 'providerId=idp.com&customParameters=' + - encodeURIComponent(fireauth.util.stringifyJSON(customParams)) + - '&scopes=thescope'; - assertEquals(url, builder.toString()); -} - - -function testOAuthUrlBuilder_twitter() { - var provider = new fireauth.TwitterAuthProvider(); - var customParams = { - 'lang': 'es' - }; - provider.setCustomParameters(customParams); - var builder = new fireauth.iframeclient.OAuthUrlBuilder( - 'example.firebaseapp.com', 'API_KEY', 'APP_NAME', 'signInWithPopup', - provider); - var url = 'https://example.firebaseapp.com/__/auth/handler?' + - 'apiKey=API_KEY&appName=APP_NAME&authType=signInWithPopup&' + - 'providerId=twitter.com&customParameters=' + - encodeURIComponent(fireauth.util.stringifyJSON(customParams)); - assertEquals(url, builder.toString()); -} - - -function testOAuthUrlBuilder_notOAuthProviderInstance() { - // instanceof doesn't always work due to renaming when compiling. Make sure - // that adding OAuth2 scopes works even if the provider class isn't an - // instance of OAuthProvider. - var provider = new fireauth.FederatedProvider('example.com'); - provider.getScopes = function() { - return ['scope1']; - }; - var customParams = { - 'lang': 'es' - }; - provider.setCustomParameters(customParams); - var builder = new fireauth.iframeclient.OAuthUrlBuilder( - 'example.firebaseapp.com', 'API_KEY', 'APP_NAME', 'signInWithPopup', - provider); - var url = 'https://example.firebaseapp.com/__/auth/handler?' + - 'apiKey=API_KEY&appName=APP_NAME&authType=signInWithPopup&' + - 'providerId=example.com&customParameters=' + - encodeURIComponent(fireauth.util.stringifyJSON(customParams)) + - '&scopes=scope1'; - assertEquals(url, builder.toString()); -} - - -/** - * Tests OAuth URL Builder with an emulator config - */ -function testOAuthUrlBuilder_withEmulatorConfig() { - var provider = new fireauth.GoogleAuthProvider(); - var emulatorConfig = { - url: "http://emulator.host:1234" - }; - var builder = new fireauth.iframeclient.OAuthUrlBuilder( - 'example.firebaseapp.com', 'API_KEY', 'APP_NAME', 'signInWithPopup', - provider, emulatorConfig); - var url = 'http://emulator.host:1234/emulator/auth/handler?' + - 'apiKey=API_KEY&appName=APP_NAME&authType=signInWithPopup&' + - 'providerId=google.com&scopes=profile'; - assertEquals(url, builder.toString()); -} - - -/** - * Tests initialization of Auth iframe and its event listeners. - */ -function testIfcHandler() { - asyncTestCase.waitForSignals(6); - // The expected iframe URL. - var expectedUrl = fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - authDomain, apiKey, appName, version); - var authEvent = new fireauth.AuthEvent( - 'unknown', '1234', 'http://www.example.com/#oauthResponse', 'SESSION_ID'); - var resp = { - 'authEvent': authEvent.toPlainObject() - }; - var invalid = undefined; - var handler1 = goog.testing.recordFunction(function() {return true;}); - var handler2 = goog.testing.recordFunction(function() {return true;}); - var handler3 = goog.testing.recordFunction(function() {return false;}); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Should not have volatile storage. - assertFalse(ifcHandler.hasVolatileStorage()); - // Should unload on redirect. - assertTrue(ifcHandler.unloadsOnRedirect()); - // Confirm expected iframe URL. - assertEquals(ifcHandler.getIframeUrl(), expectedUrl); - // Should not be initialized in constructor. - assertEquals( - 0, fireauth.iframeclient.IframeWrapper.prototype.onReady.getCallCount()); - ifcHandler.initialize().then(function() { - // Test Auth event listeners. - // Add both handlers. - ifcHandler.addAuthEventListener(handler1); - ifcHandler.addAuthEventListener(handler2); - ifcHandler.addAuthEventListener(handler3); - eventsMap[fireauth.iframeclient.IfcHandler.ReceiverEvent.AUTH_EVENT](resp) - .then(function(resolvedResp) { - assertObjectEquals({'status': 'ACK'}, resolvedResp); - asyncTestCase.signal(); - }); - // All should be called. - assertEquals(1, handler1.getCallCount()); - assertObjectEquals( - authEvent, handler1.getLastCall().getArgument(0)); - assertEquals(1, handler2.getCallCount()); - assertObjectEquals( - authEvent, handler2.getLastCall().getArgument(0)); - assertEquals(1, handler3.getCallCount()); - assertObjectEquals( - authEvent, handler3.getLastCall().getArgument(0)); - // Remove handler2. - ifcHandler.removeAuthEventListener(handler2); - eventsMap[fireauth.iframeclient.IfcHandler.ReceiverEvent.AUTH_EVENT](resp) - .then(function(resolvedResp) { - assertObjectEquals({'status': 'ACK'}, resolvedResp); - asyncTestCase.signal(); - }); - // Only handler1 and handler3 should be called. - assertEquals(2, handler1.getCallCount()); - assertObjectEquals( - authEvent, handler1.getLastCall().getArgument(0)); - assertEquals(2, handler3.getCallCount()); - assertObjectEquals( - authEvent, handler3.getLastCall().getArgument(0)); - assertEquals(1, handler2.getCallCount()); - // Remove handler1. - ifcHandler.removeAuthEventListener(handler1); - eventsMap[fireauth.iframeclient.IfcHandler.ReceiverEvent.AUTH_EVENT](resp) - .then(function(resolvedResp) { - // No handler for event so error returned. - assertObjectEquals({'status': 'ERROR'}, resolvedResp); - asyncTestCase.signal(); - }); - // Only handler3 called. - assertEquals(2, handler1.getCallCount()); - assertEquals(1, handler2.getCallCount()); - assertEquals(3, handler3.getCallCount()); - assertObjectEquals( - authEvent, handler3.getLastCall().getArgument(0)); - // Remove handler3. - ifcHandler.removeAuthEventListener(handler3); - eventsMap[fireauth.iframeclient.IfcHandler.ReceiverEvent.AUTH_EVENT](resp) - .then(function(resolvedResp) { - // No handler for event so error returned. - assertObjectEquals({'status': 'ERROR'}, resolvedResp); - asyncTestCase.signal(); - }); - // No handler called again. - assertEquals(2, handler1.getCallCount()); - assertEquals(1, handler2.getCallCount()); - assertEquals(3, handler3.getCallCount()); - - // Test when error occurs. - ifcHandler.addAuthEventListener(handler1); - eventsMap[fireauth.iframeclient.IfcHandler.ReceiverEvent.AUTH_EVENT]( - invalid).then(function(resolvedResp) { - assertObjectEquals({'status': 'ERROR'}, resolvedResp); - asyncTestCase.signal(); - }); - // Test isWebStorageSupported. - ifcHandler.isWebStorageSupported().then(function(status) { - assertTrue(status); - asyncTestCase.signal(); - }); - }); -} - - -/** - * Asserts getIframeUrl handles emulator URLs. - */ -function testIfcHandler_withEmulator() { - var emulatorConfig = { - url: 'http://emulator.test.domain:1234' - }; - // The expected iframe URL. - var expectedUrl = fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - authDomain, apiKey, appName, version, undefined, undefined, - emulatorConfig); - // Initialize the ifcHandler. - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, undefined, emulatorConfig); - // Confirm expected iframe URL. - assertEquals(ifcHandler.getIframeUrl(), expectedUrl); -} - - -function testIfcHandler_shouldNotBeInitializedEarly() { - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Can run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - // Iframe can sync web storage. - stubs.replace( - fireauth.util, - 'iframeCanSyncWebStorage', - function() { - return true; - }); - assertFalse(ifcHandler.shouldBeInitializedEarly()); - // Iframe cannot sync web storage. - stubs.replace( - fireauth.util, - 'iframeCanSyncWebStorage', - function() { - return false; - }); - assertFalse(ifcHandler.shouldBeInitializedEarly()); - // Cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - // Iframe can sync web storage. - stubs.replace( - fireauth.util, - 'iframeCanSyncWebStorage', - function() { - return true; - }); - assertFalse(ifcHandler.shouldBeInitializedEarly()); -} - - -function testIfcHandler_shouldBeInitializedEarly() { - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Cannot run in background. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - // Iframe cannot sync web storage. - stubs.replace( - fireauth.util, - 'iframeCanSyncWebStorage', - function() { - return false; - }); - assertTrue(ifcHandler.shouldBeInitializedEarly()); -} - - -function testIfcHandler_initializeAndWait_success() { - // Confirm expected endpoint config and frameworks passed to underlying RPC - // handler. - var provider = new fireauth.GoogleAuthProvider(); - var expectedFrameworks = [fireauth.util.Framework.FIREBASEUI]; - var expectedClientVersion = fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, - version, - expectedFrameworks); - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - // Simulate expected frameworks on the Auth instance corresponding to appName. - simulateAuthService(appName, null, expectedFrameworks); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - stubs.replace( - fireauth.constants, - 'getEndpointConfig', - function(opt_id) { - assertEquals(endpoint.id, opt_id); - return endpointConfig; - }); - var getAuthIframeUrl = mockControl.createMethodMock( - fireauth.iframeclient.IfcHandler, 'getAuthIframeUrl'); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - var iframeWrapper = mockControl.createStrictMock( - fireauth.iframeclient.IframeWrapper); - var iframeWrapperConstructor = mockControl.createConstructorMock( - fireauth.iframeclient, 'IframeWrapper'); - // Iframe initialized with expected endpoint ID. - getAuthIframeUrl(authDomain, apiKey, appName, ignoreArgument, - fireauth.constants.Endpoint.STAGING.id, expectedFrameworks, ignoreArgument) - .$returns('https://url'); - // Confirm iframe URL returned by getAuthIframeUrl used to initialize the - // IframeWrapper. - iframeWrapperConstructor('https://url').$returns(iframeWrapper); - iframeWrapper.registerEvent('authEvent', ignoreArgument); - iframeWrapper.onReady().$returns(goog.Promise.resolve()); - // Should be initialized with expected endpoint config and client version. - rpcHandlerConstructor(apiKey, endpointConfig, expectedClientVersion) - .$returns(rpcHandler); - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - rpcHandler.getAuthorizedDomains().$returns(goog.Promise.resolve([domain])); - mockControl.$replayAll(); - - asyncTestCase.waitForSignals(1); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, - fireauth.constants.Endpoint.STAGING.id); - ifcHandler.initializeAndWait().then(function() { - return ifcHandler.processRedirect('linkViaRedirect', provider, '1234'); - }).then(function() { - assertEquals(1, fireauth.util.goTo.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_initializeAndWait_error() { - asyncTestCase.waitForSignals(1); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - stubs.replace( - fireauth.iframeclient.IframeWrapper.prototype, - 'onReady', - function() { - return goog.Promise.reject(new Error('Network Error')); - }); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Should throw network error. - ifcHandler.initializeAndWait().thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_blocked() { - asyncTestCase.waitForSignals(1); - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.POPUP_BLOCKED); - var provider = new fireauth.GoogleAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Should throw popup blocked error. - ifcHandler.processPopup( - null, 'linkViaPopup', provider, onInit, onError, '1234', false) - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_invalidOrigin() { - asyncTestCase.waitForSignals(1); - var popupWin = { - close: goog.testing.recordFunction() - }; - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - // Assume origin is an invalid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - return goog.Promise.resolve([]); - }); - var expectedError = - new fireauth.InvalidOriginError(fireauth.util.getCurrentUrl()); - var provider = new fireauth.GoogleAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Should throw invalid origin error. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false) - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_invalidProvider() { - asyncTestCase.waitForSignals(1); - var popupWin = { - close: goog.testing.recordFunction() - }; - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - // Non OAuth provider should throw invalid provider error. - var provider = new fireauth.EmailAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Should throw invalid provider error. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false) - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_initializeAndWaitError() { - asyncTestCase.waitForSignals(1); - var popupWin = { - close: goog.testing.recordFunction() - }; - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - // Simulate error embedding the iframe. - stubs.replace( - fireauth.iframeclient.IframeWrapper.prototype, - 'onReady', - function() { - return goog.Promise.reject(new Error('Network Error')); - }); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - var provider = new fireauth.GoogleAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Should throw network error. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false) - .thenCatch(function(error) { - // Should be initialized. - assertEquals(1, onInit.getCallCount()); - // Should channel error. - assertEquals(1, onError.getCallCount()); - assertEquals(error, onError.getLastCall().getArgument(0)); - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_notAlreadyRedirected_success() { - asyncTestCase.waitForSignals(1); - var popupWin = { - close: goog.testing.recordFunction() - }; - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - var provider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaPopup', - provider, - null, - '1234', - version, - undefined, - // Check expected endpoint ID appended. - fireauth.constants.Endpoint.STAGING.id); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, - fireauth.constants.Endpoint.STAGING.id); - // Should succeed. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false) - .then(function() { - // On init should be called as the iframe is initialized. - assertEquals(1, onInit.getCallCount()); - // No error. - assertEquals(0, onError.getCallCount()); - // Popup redirected. - assertEquals( - expectedUrl, - fireauth.util.goTo.getLastCall().getArgument(0)); - assertEquals( - popupWin, - fireauth.util.goTo.getLastCall().getArgument(1)); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_notAlreadyRedirected_tenantId_success() { - asyncTestCase.waitForSignals(1); - var popupWin = { - close: goog.testing.recordFunction() - }; - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - var tenantId = '123456789012'; - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - var provider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaPopup', - provider, - null, - '1234', - version, - undefined, - // Check expected endpoint ID appended. - fireauth.constants.Endpoint.STAGING.id, - tenantId); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, - fireauth.constants.Endpoint.STAGING.id); - // Should succeed. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false, - tenantId).then(function() { - // On init should be called as the iframe is initialized. - assertEquals(1, onInit.getCallCount()); - // No error. - assertEquals(0, onError.getCallCount()); - // Popup redirected. - assertEquals( - expectedUrl, - fireauth.util.goTo.getLastCall().getArgument(0)); - assertEquals( - popupWin, - fireauth.util.goTo.getLastCall().getArgument(1)); - asyncTestCase.signal(); - }); -} - - -/** Asserts processRedirect can handle emulator URLs. */ -function testIfcHandler_processPopup_success_withEmulator() { - var emulatorConfig = { - url: 'http:/emulator.test.domain:1234' - }; - asyncTestCase.waitForSignals(1); - var popupWin = { - close: goog.testing.recordFunction() - }; - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function () { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmulatorConfig', - goog.testing.recordFunction()); - var provider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaPopup', - provider, - null, - '1234', - version, - undefined, - // Check expected endpoint ID appended. - fireauth.constants.Endpoint.STAGING.id, - undefined, - emulatorConfig); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, - fireauth.constants.Endpoint.STAGING.id, emulatorConfig); - // Should succeed. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false) - .then(function () { - // On init should be called as the iframe is initialized. - assertEquals(1, onInit.getCallCount()); - // No error. - assertEquals(0, onError.getCallCount()); - // Popup redirected. - assertEquals( - expectedUrl, - fireauth.util.goTo.getLastCall().getArgument(0)); - // Emulator config set on RpcHandler. - assertObjectEquals( - emulatorConfig, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0)); - assertEquals( - popupWin, - fireauth.util.goTo.getLastCall().getArgument(1)); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_redirected_iframeCanRunInBG_success() { - asyncTestCase.waitForSignals(1); - // Simulate that the app can run in the background but is running in an - // iframe. This typically triggers processPopup call with - // opt_alreadyRedirected set to true. This is due to the fact that sandboxed - // iframes may not be able to redirect a popup window that they opened. - // In this case, simulate the origin is whitelisted. This should succeed with - // no additional redirect call. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - var popupWin = { - close: goog.testing.recordFunction() - }; - var provider = new fireauth.GoogleAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Call with alreadyRedirected true. - // Should succeed. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', true) - .then(function() { - // On init should be called as the iframe is initialized. - assertEquals(1, onInit.getCallCount()); - // No error. - assertEquals(0, onError.getCallCount()); - // No additional redirect. - assertEquals(0, fireauth.util.goTo.getCallCount(0)); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_redirected_iframeCanRunInBG_error() { - asyncTestCase.waitForSignals(1); - // Simulate that the app can run in the background but is running in an - // iframe. This typically triggers processPopup call with - // opt_alreadyRedirected set to true. This is due to the fact that sandboxed - // iframes may not be able to redirect a popup window that they opened. - // In this case, simulate the origin is not whitelisted. This should throw the - // expected error. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return true; - }); - // Assume origin is an invalid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - return goog.Promise.resolve([]); - }); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - var popupWin = { - close: goog.testing.recordFunction() - }; - var provider = new fireauth.GoogleAuthProvider(); - var expectedError = - new fireauth.InvalidOriginError(fireauth.util.getCurrentUrl()); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Call with alreadyRedirected true. - // Should succeed. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', true) - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // On init should not be called. - assertEquals(0, onInit.getCallCount()); - // No on error call. - assertEquals(0, onError.getCallCount()); - // No redirect. - assertEquals(0, fireauth.util.goTo.getCallCount(0)); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_alreadyRedirected_success() { - asyncTestCase.waitForSignals(1); - // Simulate that the app cannot run in the background. This typically triggers - // processPopup call with opt_alreadyRedirected set to true. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - var popupWin = { - close: goog.testing.recordFunction() - }; - var provider = new fireauth.GoogleAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Call with alreadyRedirected true. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', true) - .then(function() { - // On initialization called. - assertEquals(1, onInit.getCallCount()); - // No error. - assertEquals(0, onError.getCallCount()); - // As popup already redirected, no redirect triggered. - /** @suppress {missingRequire} */ - assertEquals(0, fireauth.util.goTo.getCallCount()); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processPopup_alreadyRedirect_initializeAndWaitError() { - asyncTestCase.waitForSignals(1); - // Simulate that the app cannot run in the background. This typically triggers - // processPopup call with opt_alreadyRedirected set to true. - stubs.replace( - fireauth.util, - 'runsInBackground', - function() { - return false; - }); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - stubs.replace( - fireauth.iframeclient.IframeWrapper.prototype, - 'onReady', - function() { - return goog.Promise.reject(new Error('Network Error')); - }); - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - var popupWin = { - close: goog.testing.recordFunction() - }; - var provider = new fireauth.GoogleAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Call with alreadyRedirected true. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', true) - .then(function() { - // On initialization called. - assertEquals(1, onInit.getCallCount()); - // No popup redirect. - /** @suppress {missingRequire} */ - assertEquals(0, fireauth.util.goTo.getCallCount()); - // onError should be called in the background if an error occurs - // during embed of iframe. - ifcHandler.initializeAndWait().thenCatch(function(error) { - assertEquals(1, onError.getCallCount()); - assertObjectEquals(error, onError.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); - }); -} - - -function testIfcHandler_processPopup_networkError_then_success() { - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - var popupWin = { - close: goog.testing.recordFunction() - }; - var onInit = goog.testing.recordFunction(); - var onError = goog.testing.recordFunction(); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - var calls = 0; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - calls++; - // Simulate network error on first call. - if (calls == 1) { - return goog.Promise.reject(expectedError); - } - // Simulate valid origin on next call. - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - var provider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaPopup', - provider, - null, - '1234', - version); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // First call fails with origin check network error. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false) - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // Second call should succeed and confirm origin check not cached. - ifcHandler.processPopup( - popupWin, 'linkViaPopup', provider, onInit, onError, '1234', false) - .then(function() { - // Success on retrial. Invalid origin not cached. - assertEquals(1, onInit.getCallCount()); - assertEquals(0, onError.getCallCount()); - assertEquals( - expectedUrl, - fireauth.util.goTo.getLastCall().getArgument(0)); - assertEquals( - popupWin, - fireauth.util.goTo.getLastCall().getArgument(1)); - asyncTestCase.signal(); - }); - }); -} - - -function testIfcHandler_startPopupTimeout_webStorageNotSupported() { - stubs.replace( - fireauth.iframeclient.IfcHandler.prototype, - 'isWebStorageSupported', - function() { - return goog.Promise.resolve(false); - }); - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - var popupWin = { - close: goog.testing.recordFunction() - }; - // On error should be triggered with web storage not supported error. - var onError = function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }; - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - ifcHandler.startPopupTimeout(popupWin, onError, 1000); -} - - -function testIfcHandler_startPopupTimeout_popupClosedByUser() { - clock = new goog.testing.MockClock(true); - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.POPUP_CLOSED_BY_USER); - var popupWin = { - close: goog.testing.recordFunction() - }; - var onError = goog.testing.recordFunction(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // onError should be called with the expected popup closed by user error. - ifcHandler.startPopupTimeout(popupWin, onError, 2000).then(function() { - assertEquals(1, onError.getCallCount()); - assertObjectEquals(expectedError, onError.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); - // Exceed timeout after close to trigger popup closed by user. - popupWin.closed = true; - clock.tick(4000); -} - - -function testIfcHandler_processRedirect_invalidOrigin() { - asyncTestCase.waitForSignals(1); - // Assume origin is an invalid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - return goog.Promise.resolve([]); - }); - var expectedError = - new fireauth.InvalidOriginError(fireauth.util.getCurrentUrl()); - var provider = new fireauth.GoogleAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Invalid origin error should be thrown. - ifcHandler.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processRedirect_invalidProvider() { - asyncTestCase.waitForSignals(1); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OAUTH_PROVIDER); - // Invalid OAuth provider. - var provider = new fireauth.EmailAuthProvider(); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // Should throw invalid provider error. - ifcHandler.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processRedirect_success() { - var provider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - fireauth.util.getCurrentUrl(), - '1234', - version, - undefined, - // Check expected endpoint ID appended. - fireauth.constants.Endpoint.STAGING.id); - asyncTestCase.waitForSignals(1); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, - fireauth.constants.Endpoint.STAGING.id); - // Should succeed and redirect. - ifcHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - /** @suppress {missingRequire} */ - assertEquals( - expectedUrl, - fireauth.util.goTo.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processRedirect_tenantId_success() { - var provider = new fireauth.GoogleAuthProvider(); - var tenantId = '123456789012'; - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - fireauth.util.getCurrentUrl(), - '1234', - version, - undefined, - // Check expected endpoint ID appended. - fireauth.constants.Endpoint.STAGING.id, - tenantId); - asyncTestCase.waitForSignals(1); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, - fireauth.constants.Endpoint.STAGING.id); - // Should succeed and redirect. - ifcHandler.processRedirect('linkViaRedirect', provider, '1234', tenantId) - .then(function() { - /** @suppress {missingRequire} */ - assertEquals( - expectedUrl, - fireauth.util.goTo.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); -} - - -function testIfcHandler_processRedirect_networkError_then_success() { - asyncTestCase.waitForSignals(1); - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - var calls = 0; - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function() { - calls++; - // Simulate network error on first call. - if (calls == 1) { - return goog.Promise.reject(expectedError); - } - // Simulate valid origin on next call. - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - var provider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - fireauth.util.getCurrentUrl(), - '1234', - version); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version); - // First call fails with origin check network error. - ifcHandler.processRedirect('linkViaRedirect', provider, '1234') - .thenCatch(function(error) { - assertErrorEquals(expectedError, error); - // Second call should succeed and confirm origin check not cached. - ifcHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function() { - // Success on retrial. Redirect succeeded. - /** @suppress {missingRequire} */ - assertEquals(1, fireauth.util.goTo.getCallCount()); - /** @suppress {missingRequire} */ - assertEquals( - expectedUrl, - fireauth.util.goTo.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); - }); -} - - -/** Asserts that processRedirects works with emulator URLs. */ -function testIfcHandler_processRedirect_success_withEmulator() { - var emulatorConfig = { - url: 'http:/emulator.test.domain:1234' - }; - var provider = new fireauth.GoogleAuthProvider(); - var expectedUrl = fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - 'linkViaRedirect', - provider, - fireauth.util.getCurrentUrl(), - '1234', - version, - undefined, - // Check expected endpoint ID appended. - fireauth.constants.Endpoint.STAGING.id, - null, - emulatorConfig); - asyncTestCase.waitForSignals(1); - // Assume origin is a valid one. - stubs.replace( - fireauth.RpcHandler.prototype, - 'getAuthorizedDomains', - function () { - var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); - var domain = uri.getDomain(); - return goog.Promise.resolve([domain]); - }); - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmulatorConfig', - goog.testing.recordFunction()); - stubs.replace( - fireauth.util, - 'goTo', - goog.testing.recordFunction()); - ifcHandler = new fireauth.iframeclient.IfcHandler( - authDomain, apiKey, appName, version, - fireauth.constants.Endpoint.STAGING.id, emulatorConfig); - // Should succeed and redirect. - ifcHandler.processRedirect('linkViaRedirect', provider, '1234') - .then(function () { - /** @suppress {missingRequire} */ - assertEquals( - expectedUrl, - fireauth.util.goTo.getLastCall().getArgument(0)); - // Emulator config set on RpcHandler. - assertObjectEquals( - emulatorConfig, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0)); - asyncTestCase.signal(); - }); -} - - -/** - * Tests getAuthIframeUrl. - */ -function testGetAuthIframeUrl() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var version = '3.0.0-rc.1'; - var endpointId = 's'; - assertEquals( - 'https://subdomain.firebaseapp.com/__/auth/iframe?apiKey=apiKey1&appNa' + - 'me=appName1&v=' + encodeURIComponent(version) + '&eid=' + endpointId, - fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - authDomain, apiKey, appName, version, endpointId)); -} - - -/** - * Asserts getAuthIframeUrl handles emulator URLs. - */ -function testGetAuthIframeUrl_withEmulator() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var version = '3.0.0-rc.1'; - var endpointId = 's'; - var emulatorConfig = { - url: "http://emulator.host:1234" - }; - assertEquals( - 'http://emulator.host:1234/emulator/auth/iframe?apiKey=apiKey1&appNa' + - 'me=appName1&v=' + encodeURIComponent(version) + '&eid=' + endpointId, - fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - authDomain, - apiKey, - appName, - version, - endpointId, - null, - emulatorConfig) - ); -} - - -/** - * Tests getAuthIframeUrl with frameworks. - */ -function testGetAuthIframeUrl_frameworks() { - var expectedFrameworks = ['firebaseui', 'angularfire']; - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var version = '3.0.0-rc.1'; - var endpointId = 's'; - assertEquals( - 'https://subdomain.firebaseapp.com/__/auth/iframe?apiKey=apiKey1&appNa' + - 'me=appName1&v=' + encodeURIComponent(version) + '&eid=' + endpointId + - '&fw=' + encodeURIComponent(expectedFrameworks.join(',')), - fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - authDomain, apiKey, appName, version, endpointId, - expectedFrameworks)); -} - - -/** - * Tests getAuthIframeUrl with injections. - */ -function testGetAuthIframeUrl_injections() { - // Injecting an evil redirect for some reason. - var authDomain = 'subdomain.firebaseapp.com/?redirectUrl=evil.com#'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - assertEquals( - 'https://subdomain.firebaseapp.com%2F%3FredirectUrl%3Devil.com%23/__/a' + - 'uth/iframe?apiKey=apiKey1&appName=appName1', - fireauth.iframeclient.IfcHandler.getAuthIframeUrl( - authDomain, apiKey, appName)); -} - - -/** - * Tests getOAuthHelperWidgetUrl with redirect URL, event ID, version and no - * scopes. - */ -function testGetOAuthHelperWidgetUrl_redirectUrlEventIdVersionAndNoScopes() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var providerId = 'facebook.com'; - var provider = new fireauth.FacebookAuthProvider(); - var redirectUrl = 'http://www.example.com/redirect?a=b#c'; - var eventId = '12345678'; - var version = '3.0.0-rc.1'; - var endpointId = 's'; - var expectedWidgetUrl = 'https://subdomain.firebaseapp.com/__/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId) + - '&redirectUrl=' + encodeURIComponent(redirectUrl) + - '&eventId=' + encodeURIComponent(eventId) + - '&v=' + encodeURIComponent(version) + - '&eid=' + endpointId; - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - authType, - provider, - redirectUrl, - eventId, - version, - null, - endpointId)); -} - - -/** - * Tests getOAuthHelperWidgetUrl with redirect URL, event ID, version, tenant ID - * and no scopes. - */ -function testGetOAuthHelperWidgetUrl_redirectUrlEventIdVersionTenantId() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var providerId = 'facebook.com'; - var provider = new fireauth.FacebookAuthProvider(); - var redirectUrl = 'http://www.example.com/redirect?a=b#c'; - var eventId = '12345678'; - var version = '3.0.0-rc.1'; - var endpointId = 's'; - var tenantId = '123456789012'; - var expectedWidgetUrl = 'https://subdomain.firebaseapp.com/__/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId) + - '&redirectUrl=' + encodeURIComponent(redirectUrl) + - '&eventId=' + encodeURIComponent(eventId) + - '&v=' + encodeURIComponent(version) + - '&tid=' + encodeURIComponent(tenantId) + - '&eid=' + endpointId; - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - authType, - provider, - redirectUrl, - eventId, - version, - null, - endpointId, - tenantId)); -} - - -/** - * Tests getOAuthHelperWidgetUrl with injections. - */ -function testGetOAuthHelperWidgetUrl_injections() { - // Injecting an evil redirect for some reason. - var authDomain = 'subdomain.firebaseapp.com/?redirectUrl=evil.com#'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var provider = new fireauth.FacebookAuthProvider(); - var providerId = 'facebook.com'; - var redirectUrl = 'http://www.example.com/redirect?a=b#c'; - var eventId = '12345678'; - var expectedWidgetUrl = 'https://subdomain.firebaseapp.com%2F%3FredirectUr' + - 'l%3Devil.com%23/__/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId) + - '&redirectUrl=' + encodeURIComponent(redirectUrl) + - '&eventId=' + encodeURIComponent(eventId); - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - authType, - provider, - redirectUrl, - eventId)); -} - - -/** - * Tests getOAuthHelperWidgetUrl with scope, event ID and redirect URL. - */ -function testGetOAuthHelperWidgetUrl_redirectUrlEventIdAndScopes() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var providerId = 'facebook.com'; - var redirectUrl = 'http://www.example.com/redirect?a=b#c'; - var provider = new fireauth.FacebookAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - provider.addScope('scope3'); - var eventId = '12345678'; - var expectedWidgetUrl = 'https://subdomain.firebaseapp.com/__/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId) + - '&scopes=' + encodeURIComponent('scope1,scope2,scope3') + - '&redirectUrl=' + encodeURIComponent(redirectUrl) + - '&eventId=' + encodeURIComponent(eventId); - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - authType, - provider, - redirectUrl, - eventId)); -} - - -/** - * Tests getOAuthHelperWidgetUrl with scope, event ID, redirect URL and tenant - * ID. - */ -function testGetOAuthHelperWidgetUrl_redirectUrlEventIdTenantIdAndScopes() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var providerId = 'facebook.com'; - var redirectUrl = 'http://www.example.com/redirect?a=b#c'; - var provider = new fireauth.FacebookAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - provider.addScope('scope3'); - var eventId = '12345678'; - var tenantId = '123456789012'; - var expectedWidgetUrl = 'https://subdomain.firebaseapp.com/__/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId) + - '&scopes=' + encodeURIComponent('scope1,scope2,scope3') + - '&redirectUrl=' + encodeURIComponent(redirectUrl) + - '&eventId=' + encodeURIComponent(eventId) + - '&tid=' + encodeURIComponent(tenantId); - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - authType, - provider, - redirectUrl, - eventId, - null, - null, - null, - tenantId)); -} - - -/** - * Tests getOAuthHelperWidgetUrl with no redirect URL and no scopes. - */ -function testGetOAuthHelperWidgetUrl_noRedirectUrlAndNoScopes() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var providerId = 'facebook.com'; - var provider = new fireauth.FacebookAuthProvider(); - var expectedWidgetUrl = 'https://subdomain.firebaseapp.com/__/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId); - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, apiKey, appName, authType, provider)); -} - - -/** - * Tests getOAuthHelperWidgetUrl with scopes and no redirect URL. - */ -function testGetOAuthHelperWidgetUrl_scopesAndNoRedirectUrl() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var providerId = 'facebook.com'; - var provider = new fireauth.FacebookAuthProvider(); - provider.addScope('scope1'); - provider.addScope('scope2'); - provider.addScope('scope3'); - var expectedWidgetUrl = 'https://subdomain.firebaseapp.com/__/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId) + - '&scopes=' + encodeURIComponent('scope1,scope2,scope3'); - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, apiKey, appName, authType, provider)); -} - - -/** - * Tests getOAuthHelperWidgetUrl with Auth languageCode and frameworks. - */ -function testGetOAuthHelperWidgetUrl_frameworksAndLanguageCode() { - // Test getOAuthHelperWidgetUrl picks up Auth languageCode and frameworks. - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var providerId = 'facebook.com'; - var provider = new fireauth.FacebookAuthProvider(); - var languageCode = 'fr'; - var frameworks = ['firebaseui', 'angularfire']; - provider.addScope('scope1'); - provider.addScope('scope2'); - provider.addScope('scope3'); - simulateAuthService(appName, languageCode, frameworks); - var expectedWidgetUrl = 'https://subdomain.firebaseapp.com/__/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId) + - '&customParameters=' + encodeURIComponent( - fireauth.util.stringifyJSON({'locale': 'fr'})) + - '&scopes=' + encodeURIComponent('scope1,scope2,scope3') + - '&fw=' + encodeURIComponent(frameworks.join(',')); - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, apiKey, appName, authType, provider)); -} - - -/** - * Asserts getOAuthHelperWidgetUrl handles emulator URLs. - */ -function testGetOAuthHelperWidgetUrl_withEmulator() { - var authDomain = 'subdomain.firebaseapp.com'; - var apiKey = 'apiKey1'; - var appName = 'appName1'; - var authType = 'signInWithPopup'; - var providerId = 'facebook.com'; - var provider = new fireauth.FacebookAuthProvider(); - var emulatorConfig = { - url: 'http://emulator.test.domain:1234' - }; - var expectedWidgetUrl = 'http://emulator.test.domain:1234/' + - 'emulator/auth/handler' + - '?apiKey=' + encodeURIComponent(apiKey) + - '&appName=' + encodeURIComponent(appName) + - '&authType=' + encodeURIComponent(authType) + - '&providerId=' + encodeURIComponent(providerId); - assertEquals( - expectedWidgetUrl, - fireauth.iframeclient.IfcHandler.getOAuthHelperWidgetUrl( - authDomain, - apiKey, - appName, - authType, - provider, - null, - null, - null, - null, - null, - null, - emulatorConfig)); -} \ No newline at end of file diff --git a/packages/auth/test/iframeclient/iframewrapper_test.js b/packages/auth/test/iframeclient/iframewrapper_test.js deleted file mode 100644 index 0e5cbfa9b95..00000000000 --- a/packages/auth/test/iframeclient/iframewrapper_test.js +++ /dev/null @@ -1,515 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for dataiframe.js - */ - -goog.provide('fireauth.iframeclient.IframeWrapperTest'); - -goog.require('fireauth.iframeclient.IframeWrapper'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Uri'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.net.jsloader'); -goog.require('goog.testing.AsyncTestCase'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.mockmatchers'); - -goog.setTestOnly('fireauth.iframeclient.IframeWrapperTest'); - - -var ignoreArgument; -var mockControl; -var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); -var clock; -var gapi; -var stubs = new goog.testing.PropertyReplacer(); - -function setUp() { - clock = new goog.testing.MockClock(true); - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); -} - - -function tearDown() { - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } - // Reset GApi for each test. - gapi = null; - goog.dispose(clock); - stubs.reset(); - // Reset cached GApi loader. - fireauth.iframeclient.IframeWrapper.resetCachedGApiLoader(); -} - - -function testIframeWrapper() { - var expectedHandler = function(resp) {}; - var path = 'https://data_iframe_url'; - var iframesGetContext = mockControl.createFunctionMock('getContext'); - // Simulate gapi.iframes loaded. - gapi = window['gapi'] || {}; - gapi.iframes = { - 'Iframe': {}, - 'getContext': iframesGetContext, - 'CROSS_ORIGIN_IFRAMES_FILTER': 'CROSS_ORIGIN_IFRAMES_FILTER' - }; - var openIframe = mockControl.createFunctionMock('openIframe'); - var send = mockControl.createFunctionMock('send'); - var register = mockControl.createFunctionMock('register'); - var unregister = mockControl.createFunctionMock('unregister'); - var restyle = mockControl.createFunctionMock('restyle'); - iframesGetContext().$returns({ - 'open': openIframe - }); - openIframe(ignoreArgument, ignoreArgument).$does(function(params, onOpen) { - assertEquals(params['url'], 'https://data_iframe_url'); - assertObjectEquals(params['where'], document.body); - assertObjectEquals(params['attributes']['style'], { - 'position': 'absolute', - 'top': '-100px', - 'width': '1px', - 'height': '1px' - }); - assertTrue(params['dontclear']); - onOpen({ - 'send': send, - 'register': register, - 'unregister': unregister, - 'restyle': restyle, - 'ping': function(callback, opt_data) { - // Successfully embedded. - callback(); - return new goog.Promise(function(resolve, reject) {}); - } - }); - }).$once(); - restyle({'setHideOnLeave': false}).$once(); - - send( - 'messageType', - {'type': 'messageType', 'field1': 'value1', 'field2': 'value2'}, - ignoreArgument, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER) - .$does(function(type, message, resolve) { - // Iframe should be ready. - assertTrue(iframeReady); - resolve({'status': 'OK'}); - }) - .$once(); - - register( - 'eventName', ignoreArgument, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER) - .$does(function(eventName, handler) { - // Iframe should be ready. - assertTrue(iframeReady); - assertEquals(expectedHandler, handler); - }) - .$once(); - - unregister('eventName', ignoreArgument) - .$does(function(eventName, handler) { - // Iframe should be ready. - assertTrue(iframeReady); - assertEquals(expectedHandler, handler); - }) - .$once(); - - mockControl.$replayAll(); - asyncTestCase.waitForSignals(1); - - // Test initialization of data iframe. - var iframeWrapper = new fireauth.iframeclient.IframeWrapper(path); - // Iframe wrapper should become ready. - iframeWrapper.onReady().then(function() { iframeReady = true; }); - assertEquals(path, iframeWrapper.getPath_()); - // sendMessage. - iframeWrapper.sendMessage({ - 'type': 'messageType', - 'field1': 'value1', - 'field2': 'value2' - }).then(function(response) { - assertObjectEquals( - {'status': 'OK'}, - response); - asyncTestCase.signal(); - }); - // Flag to track iframe readiness. - var iframeReady = false; - // registerEvent. - iframeWrapper.registerEvent('eventName', expectedHandler); - // unregisterEvent. - iframeWrapper.unregisterEvent('eventName', expectedHandler); -} - - -function testIframeWrapper_failedToOpen() { - // Test when iframe fails to open. - var path = 'https://data_iframe_url'; - var iframesGetContext = mockControl.createFunctionMock('getContext'); - // Simulate gapi.iframes loaded. - gapi = window['gapi'] || {}; - gapi.iframes = { - 'Iframe': {}, - 'getContext': iframesGetContext, - 'CROSS_ORIGIN_IFRAMES_FILTER': 'CROSS_ORIGIN_IFRAMES_FILTER' - }; - var openIframe = mockControl.createFunctionMock('openIframe'); - var send = mockControl.createFunctionMock('send'); - var register = mockControl.createFunctionMock('register'); - var unregister = mockControl.createFunctionMock('unregister'); - var restyle = mockControl.createFunctionMock('restyle'); - iframesGetContext().$returns({'open': openIframe}); - openIframe(ignoreArgument, ignoreArgument) - .$does(function(params, onOpen) { - assertEquals(params['url'], 'https://data_iframe_url'); - assertObjectEquals(params['where'], document.body); - assertObjectEquals(params['attributes']['style'], { - 'position': 'absolute', - 'top': '-100px', - 'width': '1px', - 'height': '1px' - }); - assertTrue(params['dontclear']); - onOpen({ - 'send': send, - 'register': register, - 'unregister': unregister, - 'restyle': restyle, - // Unresponsive ping. - 'ping': function() { - return new goog.Promise(function(resolve, reject) {}); - } - }); - }) - .$once(); - restyle({'setHideOnLeave': false}).$once(); - - mockControl.$replayAll(); - - // Test initialization of data iframe. - asyncTestCase.waitForSignals(2); - var iframeWrapper = new fireauth.iframeclient.IframeWrapper(path); - // Iframe wrapper should not become ready and timeout. - iframeWrapper.onReady().thenCatch(function(error) { - assertEquals('Network Error', error.message); - asyncTestCase.signal(); - }); - iframeWrapper - .sendMessage( - {'type': 'messageType', 'field1': 'value1', 'field2': 'value2'}) - .thenCatch(function(error) { - assertEquals('Network Error', error.message); - asyncTestCase.signal(); - }); - // Simulate iframe ping is not responsive. - clock.tick(10000); -} - - -function testIframeWrapper_offline() { - // Test when iframe fails to open due to app being offline. - // Simulate app offline. - stubs.reset(); - stubs.replace( - fireauth.util, - 'isOnline', - function() {return false;}); - var path = 'https://data_iframe_url'; - - // Test initialization of data iframe. - asyncTestCase.waitForSignals(2); - var iframeWrapper = new fireauth.iframeclient.IframeWrapper(path); - // Iframe wrapper should not become ready and timeout as the app is offline. - // Mockclock is already set in setUp and does not tick. This means the call - // is getting rejected immediately and not listening to any timer. - iframeWrapper.onReady().thenCatch(function(error) { - assertEquals('Network Error', error.message); - asyncTestCase.signal(); - }); - // Simulate short timeout when navigator.onLine is false. - clock.tick(5000); - iframeWrapper - .sendMessage( - {'type': 'messageType', 'field1': 'value1', 'field2': 'value2'}) - .thenCatch(function(error) { - assertEquals('Network Error', error.message); - asyncTestCase.signal(); - }); -} - - -/** - * Simulate successful gapi.iframes being loaded. - * @param {function()} iframesGetContext The iframes getContext mock function. - */ -function simulateSuccessfulGapiIframesLoading(iframesGetContext) { - var gapiLoadCounter = 0; - var jsloaderCounter = 0; - var setGapiLoader = function() { - gapi.load = function(features, options) { - // Run asynchronously to give a chance for multiple parallel calls to be - // caught. - goog.Promise.resolve().then(function() { - // gapi.load should never try to load successfully more than once and - // the successful result should be cached and returned on successive - // calls. - gapiLoadCounter++; - assertEquals(1, gapiLoadCounter); - // gapi.load should load gapi.iframes. - var callback = options['callback']; - gapi.iframes = { - 'Iframe': {}, - 'getContext': iframesGetContext, - 'CROSS_ORIGIN_IFRAMES_FILTER': 'CROSS_ORIGIN_IFRAMES_FILTER' - }; - callback(); - }); - }; - }; - if (!gapi) { - // GApi not available, it will try to load api.js and then gapi.iframes. - stubs.replace(goog.net.jsloader, 'safeLoad', function(url) { - // Run asynchronously to give a chance for multiple parallel calls to - // be caught. - return goog.Promise.resolve().then(function() { - // jsloader should never try to load successfully more than once and - // the successful result should be cached and returned on successive - // calls. - jsloaderCounter++; - assertEquals(1, jsloaderCounter); - // After successful loading of API. - gapi = {}; - // Set gapi.load. - setGapiLoader(); - // Parse URL and get onload cb name. - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var cbName = uri.getParameterValue('onload'); - // Run onload callback. - goog.global[cbName](); - }); - }); - } else if (!gapi.iframes) { - // gapi.load available, it will try to load gapi.iframes. - setGapiLoader(); - } -} - - -function testIframeWrapper_gapiNotLoadedError() { - // Test when GApi fails to load. - gapi = null; - stubs.replace(goog.net.jsloader, 'safeLoad', function(url) { - return goog.Promise.reject(); - }); - var path = 'https://data_iframe_url'; - var iframesGetContext = mockControl.createFunctionMock('getContext'); - var openIframe = mockControl.createFunctionMock('openIframe'); - var send = mockControl.createFunctionMock('send'); - var register = mockControl.createFunctionMock('register'); - var unregister = mockControl.createFunctionMock('unregister'); - var restyle = mockControl.createFunctionMock('restyle'); - iframesGetContext().$returns({'open': openIframe}); - openIframe(ignoreArgument, ignoreArgument) - .$does(function(params, onOpen) { - assertEquals(params['url'], 'https://data_iframe_url'); - assertObjectEquals(params['where'], document.body); - assertObjectEquals(params['attributes']['style'], { - 'position': 'absolute', - 'top': '-100px', - 'width': '1px', - 'height': '1px' - }); - assertTrue(params['dontclear']); - onOpen({ - 'send': send, - 'register': register, - 'unregister': unregister, - 'restyle': restyle, - 'ping': function(callback) { - callback(); - return new goog.Promise(function(resolve, reject) {}); - } - }); - }) - .$once(); - restyle({'setHideOnLeave': false}).$once(); - - mockControl.$replayAll(); - - // Test initialization of data iframe. - asyncTestCase.waitForSignals(2); - var iframeWrapper = new fireauth.iframeclient.IframeWrapper(path); - // Iframe wrapper should not become ready as api.js fails to load. - iframeWrapper.onReady().thenCatch(function(error) { - assertEquals('Network Error', error.message); - asyncTestCase.signal(); - }); - iframeWrapper - .sendMessage( - {'type': 'messageType', 'field1': 'value1', 'field2': 'value2'}) - .thenCatch(function(error) { - assertEquals('Network Error', error.message); - // Try again and make sure failing result was not cached. - // This time gapi.iframes will load correctly. - simulateSuccessfulGapiIframesLoading(iframesGetContext); - var iframeWrapper2 = new fireauth.iframeclient.IframeWrapper(path); - // This time it should succeed. - iframeWrapper2.onReady().then(function() { asyncTestCase.signal(); }); - }); -} - - -function testIframeWrapper_gapiDotLoadError() { - var path = 'https://data_iframe_url'; - var resetUnloadedGapiModules = - mockControl.createFunctionMock('resetUnloadedGapiModules'); - gapi = {}; - // Simulate error while loading gapi.iframes. - gapi.load = function(features, options) { - assertEquals('gapi.iframes', features); - options['ontimeout'](); - }; - // Record fireauth.util.resetUnloadedGapiModules. - stubs.replace( - fireauth.util, 'resetUnloadedGapiModules', resetUnloadedGapiModules); - // Called once to reset any unloaded module the developer may have requested. - resetUnloadedGapiModules(); - // Called on first gapi.iframe load timeout. - resetUnloadedGapiModules(); - // Called before second gapi.iframe load attempt. - resetUnloadedGapiModules(); - var iframesGetContext = mockControl.createFunctionMock('getContext'); - var openIframe = mockControl.createFunctionMock('openIframe'); - var send = mockControl.createFunctionMock('send'); - var register = mockControl.createFunctionMock('register'); - var unregister = mockControl.createFunctionMock('unregister'); - var restyle = mockControl.createFunctionMock('restyle'); - iframesGetContext().$returns({'open': openIframe}); - openIframe(ignoreArgument, ignoreArgument) - .$does(function(params, onOpen) { - assertEquals(params['url'], 'https://data_iframe_url'); - assertObjectEquals(params['where'], document.body); - assertObjectEquals(params['attributes']['style'], { - 'position': 'absolute', - 'top': '-100px', - 'width': '1px', - 'height': '1px' - }); - assertTrue(params['dontclear']); - onOpen({ - 'send': send, - 'register': register, - 'unregister': unregister, - 'restyle': restyle, - 'ping': function(callback) { - callback(); - return new goog.Promise(function(resolve, reject) {}); - } - }); - }) - .$once(); - restyle({'setHideOnLeave': false}).$once(); - mockControl.$replayAll(); - - // Test initialization of data iframe. - asyncTestCase.waitForSignals(2); - var iframeWrapper = new fireauth.iframeclient.IframeWrapper(path); - // Iframe wrapper should should fail due to error in loading gapi.iframes. - iframeWrapper.onReady().thenCatch(function(error) { - assertEquals('Network Error', error.message); - asyncTestCase.signal(); - }); - iframeWrapper - .sendMessage( - {'type': 'messageType', 'field1': 'value1', 'field2': 'value2'}) - .thenCatch(function(error) { - assertEquals('Network Error', error.message); - // Try again and make sure failing result was not cached. - // Simulate successful loading of gapi.iframes this time. - simulateSuccessfulGapiIframesLoading(iframesGetContext); - var iframeWrapper2 = new fireauth.iframeclient.IframeWrapper(path); - // This should succeed. - iframeWrapper2.onReady().then(function() { asyncTestCase.signal(); }); - }); -} - - -function testIframeWrapper_multipleInstances() { - // Tests when multiple iframe wrapper instance initialized that only one - // shared gapi.load attempt is called underneath. - var path = 'https://data_iframe_url'; - gapi = {}; - var iframesGetContext = mockControl.createFunctionMock('getContext'); - var openIframe = mockControl.createFunctionMock('openIframe'); - var send = mockControl.createFunctionMock('send'); - var register = mockControl.createFunctionMock('register'); - var unregister = mockControl.createFunctionMock('unregister'); - var restyle = mockControl.createFunctionMock('restyle'); - // Requests should be called twice. - for (var i = 0; i < 2; i++) { - iframesGetContext().$returns({'open': openIframe}); - openIframe(ignoreArgument, ignoreArgument) - .$does(function(params, onOpen) { - assertEquals(params['url'], 'https://data_iframe_url'); - assertObjectEquals(params['where'], document.body); - assertObjectEquals(params['attributes']['style'], { - 'position': 'absolute', - 'top': '-100px', - 'width': '1px', - 'height': '1px' - }); - assertTrue(params['dontclear']); - onOpen({ - 'send': send, - 'register': register, - 'unregister': unregister, - 'restyle': restyle, - 'ping': function(callback) { - callback(); - return new goog.Promise(function(resolve, reject) {}); - } - }); - }) - .$once(); - restyle({'setHideOnLeave': false}).$once(); - } - mockControl.$replayAll(); - - // Initialize 2 iframes. Both should resolve. - asyncTestCase.waitForSignals(2); - // Underneath this will check that gapi.load/jsloader aren't called more than - // once. - simulateSuccessfulGapiIframesLoading(iframesGetContext); - var iframeWrapper = new fireauth.iframeclient.IframeWrapper(path); - iframeWrapper.onReady().then(function() { - asyncTestCase.signal(); - }); - var iframeWrapper2 = new fireauth.iframeclient.IframeWrapper(path); - iframeWrapper2.onReady().then(function() { - asyncTestCase.signal(); - }); -} diff --git a/packages-exp/auth-exp/test/integration/flows/anonymous.test.ts b/packages/auth/test/integration/flows/anonymous.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/flows/anonymous.test.ts rename to packages/auth/test/integration/flows/anonymous.test.ts index 00aaea9d882..1c744b251fc 100644 --- a/packages-exp/auth-exp/test/integration/flows/anonymous.test.ts +++ b/packages/auth/test/integration/flows/anonymous.test.ts @@ -29,7 +29,7 @@ import { updatePassword, Auth, OperationType -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { FirebaseError } from '@firebase/util'; import { diff --git a/packages-exp/auth-exp/test/integration/flows/custom.local.test.ts b/packages/auth/test/integration/flows/custom.local.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/flows/custom.local.test.ts rename to packages/auth/test/integration/flows/custom.local.test.ts index 949e4f3589d..6ffbb18afc7 100644 --- a/packages-exp/auth-exp/test/integration/flows/custom.local.test.ts +++ b/packages/auth/test/integration/flows/custom.local.test.ts @@ -30,7 +30,7 @@ import { updateEmail, updatePassword, updateProfile -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { FirebaseError } from '@firebase/util'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; diff --git a/packages-exp/auth-exp/test/integration/flows/email.test.ts b/packages/auth/test/integration/flows/email.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/flows/email.test.ts rename to packages/auth/test/integration/flows/email.test.ts index 88bee795d6b..bb73d95c383 100644 --- a/packages-exp/auth-exp/test/integration/flows/email.test.ts +++ b/packages/auth/test/integration/flows/email.test.ts @@ -30,7 +30,7 @@ import { OperationType, UserCredential, getAdditionalUserInfo -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { FirebaseError } from '@firebase/util'; import { diff --git a/packages-exp/auth-exp/test/integration/flows/idp.local.test.ts b/packages/auth/test/integration/flows/idp.local.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/flows/idp.local.test.ts rename to packages/auth/test/integration/flows/idp.local.test.ts index 920b2030e35..9262c84c17c 100644 --- a/packages-exp/auth-exp/test/integration/flows/idp.local.test.ts +++ b/packages/auth/test/integration/flows/idp.local.test.ts @@ -32,7 +32,7 @@ import { updateEmail, updatePassword, updateProfile -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { FirebaseError } from '@firebase/util'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; diff --git a/packages-exp/auth-exp/test/integration/flows/oob.local.test.ts b/packages/auth/test/integration/flows/oob.local.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/flows/oob.local.test.ts rename to packages/auth/test/integration/flows/oob.local.test.ts index 3d3347f9d84..e7fd54a0e8b 100644 --- a/packages-exp/auth-exp/test/integration/flows/oob.local.test.ts +++ b/packages/auth/test/integration/flows/oob.local.test.ts @@ -40,7 +40,7 @@ import { updatePassword, verifyBeforeUpdateEmail, verifyPasswordResetCode -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { FirebaseError } from '@firebase/util'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; diff --git a/packages-exp/auth-exp/test/integration/flows/phone.test.ts b/packages/auth/test/integration/flows/phone.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/flows/phone.test.ts rename to packages/auth/test/integration/flows/phone.test.ts index f5095fda81c..c938964f241 100644 --- a/packages-exp/auth-exp/test/integration/flows/phone.test.ts +++ b/packages/auth/test/integration/flows/phone.test.ts @@ -34,7 +34,7 @@ import { UserCredential, signInWithCredential, ConfirmationResult -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { FirebaseError } from '@firebase/util'; import { diff --git a/packages-exp/auth-exp/test/integration/webdriver/anonymous.test.ts b/packages/auth/test/integration/webdriver/anonymous.test.ts similarity index 97% rename from packages-exp/auth-exp/test/integration/webdriver/anonymous.test.ts rename to packages/auth/test/integration/webdriver/anonymous.test.ts index 8d30329dd43..7afb33533e4 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/anonymous.test.ts +++ b/packages/auth/test/integration/webdriver/anonymous.test.ts @@ -16,7 +16,7 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -import { OperationType, UserCredential } from '@firebase/auth-exp'; +import { OperationType, UserCredential } from '@firebase/auth'; import { expect } from 'chai'; import { AnonFunction } from './util/functions'; import { browserDescribe } from './util/test_runner'; diff --git a/packages-exp/auth-exp/test/integration/webdriver/compat/firebaseui.test.ts b/packages/auth/test/integration/webdriver/compat/firebaseui.test.ts similarity index 100% rename from packages-exp/auth-exp/test/integration/webdriver/compat/firebaseui.test.ts rename to packages/auth/test/integration/webdriver/compat/firebaseui.test.ts diff --git a/packages-exp/auth-exp/test/integration/webdriver/persistence.test.ts b/packages/auth/test/integration/webdriver/persistence.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/webdriver/persistence.test.ts rename to packages/auth/test/integration/webdriver/persistence.test.ts index 4b64b7a8239..3ec3f553281 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/persistence.test.ts +++ b/packages/auth/test/integration/webdriver/persistence.test.ts @@ -16,7 +16,7 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -import { UserCredential } from '@firebase/auth-exp'; +import { UserCredential } from '@firebase/auth'; import { expect } from 'chai'; import { createAnonAccount } from '../../helpers/integration/emulator_rest_helpers'; import { API_KEY } from '../../helpers/integration/settings'; diff --git a/packages-exp/auth-exp/test/integration/webdriver/popup.test.ts b/packages/auth/test/integration/webdriver/popup.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/webdriver/popup.test.ts rename to packages/auth/test/integration/webdriver/popup.test.ts index 1b3146d7c5a..0ef1f0cb49d 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/popup.test.ts +++ b/packages/auth/test/integration/webdriver/popup.test.ts @@ -21,7 +21,7 @@ import { UserCredential, User, OAuthCredential -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { expect, use } from 'chai'; import { IdPPage } from './util/idp_page'; import * as chaiAsPromised from 'chai-as-promised'; diff --git a/packages-exp/auth-exp/test/integration/webdriver/redirect.test.ts b/packages/auth/test/integration/webdriver/redirect.test.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/webdriver/redirect.test.ts rename to packages/auth/test/integration/webdriver/redirect.test.ts index 26dfea0269f..4124241d630 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/redirect.test.ts +++ b/packages/auth/test/integration/webdriver/redirect.test.ts @@ -21,7 +21,7 @@ import { UserCredential, User, OAuthCredential -} from '@firebase/auth-exp'; +} from '@firebase/auth'; import { expect, use } from 'chai'; import { IdPPage } from './util/idp_page'; import * as chaiAsPromised from 'chai-as-promised'; diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/anonymous.js b/packages/auth/test/integration/webdriver/static/anonymous.js similarity index 92% rename from packages-exp/auth-exp/test/integration/webdriver/static/anonymous.js rename to packages/auth/test/integration/webdriver/static/anonymous.js index dbc853b6394..2b01bddbd04 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/static/anonymous.js +++ b/packages/auth/test/integration/webdriver/static/anonymous.js @@ -15,7 +15,7 @@ * limitations under the License. */ -import { signInAnonymously } from '@firebase/auth-exp'; +import { signInAnonymously } from '@firebase/auth'; export async function anonymousSignIn() { const userCred = await signInAnonymously(auth); diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/core.js b/packages/auth/test/integration/webdriver/static/core.js similarity index 100% rename from packages-exp/auth-exp/test/integration/webdriver/static/core.js rename to packages/auth/test/integration/webdriver/static/core.js diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/email.js b/packages/auth/test/integration/webdriver/static/email.js similarity index 91% rename from packages-exp/auth-exp/test/integration/webdriver/static/email.js rename to packages/auth/test/integration/webdriver/static/email.js index b13bcb2924d..ab95ef5139f 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/static/email.js +++ b/packages/auth/test/integration/webdriver/static/email.js @@ -15,7 +15,7 @@ * limitations under the License. */ -import { createUserWithEmailAndPassword } from '@firebase/auth-exp'; +import { createUserWithEmailAndPassword } from '@firebase/auth'; const TEST_PASSWORD = 'password'; diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/index.html b/packages/auth/test/integration/webdriver/static/index.html similarity index 100% rename from packages-exp/auth-exp/test/integration/webdriver/static/index.html rename to packages/auth/test/integration/webdriver/static/index.html diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/index.js b/packages/auth/test/integration/webdriver/static/index.js similarity index 95% rename from packages-exp/auth-exp/test/integration/webdriver/static/index.js rename to packages/auth/test/integration/webdriver/static/index.js index 92149f0fbd6..86471a6bd31 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/static/index.js +++ b/packages/auth/test/integration/webdriver/static/index.js @@ -21,8 +21,8 @@ import * as core from './core'; import * as popup from './popup'; import * as email from './email'; import * as persistence from './persistence'; -import { initializeApp } from '@firebase/app-exp'; -import { getAuth, connectAuthEmulator } from '@firebase/auth-exp'; +import { initializeApp } from '@firebase/app'; +import { getAuth, connectAuthEmulator } from '@firebase/auth'; window.core = core; window.anonymous = anonymous; diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/persistence.js b/packages/auth/test/integration/webdriver/static/persistence.js similarity index 99% rename from packages-exp/auth-exp/test/integration/webdriver/static/persistence.js rename to packages/auth/test/integration/webdriver/static/persistence.js index d2bb4fb89b5..a89b48d0b6d 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/static/persistence.js +++ b/packages/auth/test/integration/webdriver/static/persistence.js @@ -19,7 +19,7 @@ import { browserSessionPersistence, indexedDBLocalPersistence, inMemoryPersistence -} from '@firebase/auth-exp'; +} from '@firebase/auth'; const INDEXED_DB_NAME = 'firebaseLocalStorageDb'; diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/popup.js b/packages/auth/test/integration/webdriver/static/popup.js similarity index 98% rename from packages-exp/auth-exp/test/integration/webdriver/static/popup.js rename to packages/auth/test/integration/webdriver/static/popup.js index 8b06048976a..11a439dbf8c 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/static/popup.js +++ b/packages/auth/test/integration/webdriver/static/popup.js @@ -24,7 +24,7 @@ import { reauthenticateWithPopup, signInWithCredential, signInWithPopup -} from '@firebase/auth-exp'; +} from '@firebase/auth'; // These functions are a little funky: WebDriver relies on callbacks to // pass data back to the main Node process. Because of that setup, we can't diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/redirect.js b/packages/auth/test/integration/webdriver/static/redirect.js similarity index 98% rename from packages-exp/auth-exp/test/integration/webdriver/static/redirect.js rename to packages/auth/test/integration/webdriver/static/redirect.js index 5ef2353f0b4..74ba821550f 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/static/redirect.js +++ b/packages/auth/test/integration/webdriver/static/redirect.js @@ -25,7 +25,7 @@ import { reauthenticateWithRedirect, signInWithCredential, signInWithRedirect -} from '@firebase/auth-exp'; +} from '@firebase/auth'; let redirectCred = null; let errorCred = null; diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/rollup.config.js b/packages/auth/test/integration/webdriver/static/rollup.config.js similarity index 94% rename from packages-exp/auth-compat-exp/test/integration/webdriver/static/rollup.config.js rename to packages/auth/test/integration/webdriver/static/rollup.config.js index d48a1208ff1..90adada6615 100644 --- a/packages-exp/auth-compat-exp/test/integration/webdriver/static/rollup.config.js +++ b/packages/auth/test/integration/webdriver/static/rollup.config.js @@ -17,7 +17,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'; -// This is run from the auth-exp package.json +// This is run from the auth package.json export default { input: ['test/integration/webdriver/static/index.js'], output: { diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/auth_driver.ts b/packages/auth/test/integration/webdriver/util/auth_driver.ts similarity index 99% rename from packages-exp/auth-exp/test/integration/webdriver/util/auth_driver.ts rename to packages/auth/test/integration/webdriver/util/auth_driver.ts index 639514dc4ad..9d57f91c0cf 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/util/auth_driver.ts +++ b/packages/auth/test/integration/webdriver/util/auth_driver.ts @@ -16,7 +16,7 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -import { Auth, User, Persistence } from '@firebase/auth-exp'; +import { Auth, User, Persistence } from '@firebase/auth'; import { Builder, Condition, WebDriver } from 'selenium-webdriver'; import { resetEmulator } from '../../../helpers/integration/emulator_rest_helpers'; import { diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/functions.ts b/packages/auth/test/integration/webdriver/util/functions.ts similarity index 100% rename from packages-exp/auth-exp/test/integration/webdriver/util/functions.ts rename to packages/auth/test/integration/webdriver/util/functions.ts diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/idp_page.ts b/packages/auth/test/integration/webdriver/util/idp_page.ts similarity index 100% rename from packages-exp/auth-exp/test/integration/webdriver/util/idp_page.ts rename to packages/auth/test/integration/webdriver/util/idp_page.ts diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/js_load_condition.ts b/packages/auth/test/integration/webdriver/util/js_load_condition.ts similarity index 100% rename from packages-exp/auth-exp/test/integration/webdriver/util/js_load_condition.ts rename to packages/auth/test/integration/webdriver/util/js_load_condition.ts diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/test_runner.ts b/packages/auth/test/integration/webdriver/util/test_runner.ts similarity index 100% rename from packages-exp/auth-exp/test/integration/webdriver/util/test_runner.ts rename to packages/auth/test/integration/webdriver/util/test_runner.ts diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/test_server.ts b/packages/auth/test/integration/webdriver/util/test_server.ts similarity index 96% rename from packages-exp/auth-exp/test/integration/webdriver/util/test_server.ts rename to packages/auth/test/integration/webdriver/util/test_server.ts index 7d7e7ce057f..77a66e0d961 100644 --- a/packages-exp/auth-exp/test/integration/webdriver/util/test_server.ts +++ b/packages/auth/test/integration/webdriver/util/test_server.ts @@ -23,7 +23,7 @@ const PORT_NUMBER = '4100'; const INTEGRATION_TEST_ASSETS = express.static( path.join( - // process.env.PWD == packages-exp/auth-exp + // process.env.PWD == packages-exp/auth process.env.PWD!, 'test/integration/webdriver/static' ) diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/ui_page.ts b/packages/auth/test/integration/webdriver/util/ui_page.ts similarity index 100% rename from packages-exp/auth-exp/test/integration/webdriver/util/ui_page.ts rename to packages/auth/test/integration/webdriver/util/ui_page.ts diff --git a/packages/auth/test/messagechannel/postmessager_test.js b/packages/auth/test/messagechannel/postmessager_test.js deleted file mode 100644 index 96a65600228..00000000000 --- a/packages/auth/test/messagechannel/postmessager_test.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for postmessager.js. - */ - -goog.provide('fireauth.messagechannel.PostMessagerTest'); - -goog.require('fireauth.messagechannel.WindowPostMessager'); -goog.require('fireauth.messagechannel.WorkerClientPostMessager'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.messagechannel.PostMessagerTest'); - - -var mockControl; - - -function setUp() { - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); -} - - -function tearDown() { - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } -} - - -function testWindowPostMessager_defaultTargetOrigin() { - var message = {'a': 1, 'b': 2}; - var transfer = [{'tranferable1': {}}, {'tranferable2': {}}]; - var targetOrigin = '*'; - var postMessage = mockControl.createFunctionMock('postMessage'); - postMessage(message, targetOrigin, transfer).$once(); - mockControl.$replayAll(); - - var windowPostMessager = new fireauth.messagechannel.WindowPostMessager( - {'postMessage': postMessage}); - windowPostMessager.postMessage(message, transfer); -} - - -function testWindowPostMessager_explicitTargetOrigin() { - var message = {'a': 1, 'b': 2}; - var transfer = [{'tranferable1': {}}, {'tranferable2': {}}]; - var targetOrigin = 'http://www.example.com'; - var postMessage = mockControl.createFunctionMock('postMessage'); - postMessage(message, targetOrigin, transfer).$once(); - mockControl.$replayAll(); - - var windowPostMessager = new fireauth.messagechannel.WindowPostMessager( - {'postMessage': postMessage}, targetOrigin); - windowPostMessager.postMessage(message, transfer); -} - - -function testWorkerClientPostMessager() { - var message = {'a': 1, 'b': 2}; - var transfer = [{'tranferable1': {}}, {'tranferable2': {}}]; - var postMessage = mockControl.createFunctionMock('postMessage'); - postMessage(message, transfer); - mockControl.$replayAll(); - - var workerClientPostMessager = - new fireauth.messagechannel.WorkerClientPostMessager( - {'postMessage': postMessage}); - workerClientPostMessager.postMessage(message, transfer); -} diff --git a/packages/auth/test/messagechannel/receiver_test.js b/packages/auth/test/messagechannel/receiver_test.js deleted file mode 100644 index 9b44968ca55..00000000000 --- a/packages/auth/test/messagechannel/receiver_test.js +++ /dev/null @@ -1,479 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for receiver.js. - */ - -goog.provide('fireauth.messagechannel.ReceiverTest'); - -goog.require('fireauth.messagechannel.Receiver'); -goog.require('goog.Promise'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventTarget'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.messagechannel.ReceiverTest'); - - -var mockControl; - - -function setUp() { - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); -} - - -function tearDown() { - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } -} - - -function testReceiver_singleHandler_success() { - // Used to prevent the test from finishing prematurely before the underlying - // logic completes. - var sendCompletionSignal; - var eventType = 'eventType1'; - var eventId = '1234'; - var origin = 'https://www.example.com'; - var data = { - 'a': 1, - 'b': 2 - }; - var expectedResponse = { - 'success': true - }; - var eventTarget = new goog.events.EventTarget(); - var postMessage = mockControl.createFunctionMock('postMessage'); - var handler1 = mockControl.createFunctionMock('handler1'); - var handler2 = mockControl.createFunctionMock('handler2'); - var event = new goog.events.Event('message'); - event.origin = origin; - event.ports = [{'postMessage': postMessage}]; - event.data = { - 'eventId': eventId, - 'eventType': eventType, - 'data': data - }; - postMessage({ - 'status': 'ack', - 'eventId': eventId, - 'eventType': eventType, - 'response': null - }).$once(); - handler1(origin, data).$does(function() { - return expectedResponse; - }).$once(); - postMessage({ - 'status': 'done', - 'eventId': eventId, - 'eventType': eventType, - 'response': [{ - 'fulfilled': true, - 'value': expectedResponse - }] - }).$does(function() { - sendCompletionSignal(); - }).$once(); - handler2().$never(); - mockControl.$replayAll(); - - var receiver = new fireauth.messagechannel.Receiver(eventTarget); - assertTrue(receiver.isListeningTo(eventTarget)); - assertFalse(receiver.isListeningTo(new goog.events.EventTarget())); - receiver.subscribe(eventType, handler1); - receiver.subscribe('unsupportedEvent', handler2); - // Simulate incoming sender message event. - eventTarget.dispatchEvent(event); - receiver.unsubscribe(eventType, handler1); - // Second event should not have any effect. - eventTarget.dispatchEvent(event); - - return new goog.Promise(function(resolve, reject) { - sendCompletionSignal = resolve; - }); -} - - -function testReceiver_singleHandler_error() { - var sendCompletionSignal; - var eventType = 'eventType1'; - var eventId = '1234'; - var origin = 'https://www.example.com'; - var data = { - 'a': 1, - 'b': 2 - }; - var expectedError = new Error('some error occurred'); - var eventTarget = new goog.events.EventTarget(); - var postMessage = mockControl.createFunctionMock('postMessage'); - var handler1 = mockControl.createFunctionMock('handler1'); - var handler2 = mockControl.createFunctionMock('handler2'); - var event = new goog.events.Event('message'); - event.origin = origin; - event.ports = [{'postMessage': postMessage}]; - event.data = { - 'eventId': eventId, - 'eventType': eventType, - 'data': data - }; - postMessage({ - 'status': 'ack', - 'eventId': eventId, - 'eventType': eventType, - 'response': null - }).$once(); - handler1(origin, data).$does(function() { - throw expectedError; - }).$once(); - postMessage({ - 'status': 'done', - 'eventId': eventId, - 'eventType': eventType, - 'response': [{ - 'fulfilled': false, - 'reason': expectedError.message - }] - }).$does(function() { - sendCompletionSignal(); - }).$once(); - handler2().$never(); - mockControl.$replayAll(); - - var receiver = new fireauth.messagechannel.Receiver(eventTarget); - receiver.subscribe(eventType, handler1); - receiver.subscribe('unsupportedEvent', handler2); - // Simulate incoming sender message event. - eventTarget.dispatchEvent(event); - receiver.unsubscribe(eventType, handler1); - // Second event should not have any effect. - eventTarget.dispatchEvent(event); - - return new goog.Promise(function(resolve, reject) { - sendCompletionSignal = resolve; - }); -} - - -function testReceiver_multipleHandler_singleEvent_success() { - var sendCompletionSignal; - var eventType = 'eventType1'; - var eventId = '1234'; - var origin = 'https://www.example.com'; - var data = { - 'a': 1, - 'b': 2 - }; - var expectedResponse1 = { - 'c': 3 - }; - var expectedResponse2 = { - 'd': 4 - }; - var eventTarget = new goog.events.EventTarget(); - var postMessage = mockControl.createFunctionMock('postMessage'); - var handler1 = mockControl.createFunctionMock('handler1'); - var handler2 = mockControl.createFunctionMock('handler2'); - var handler3 = mockControl.createFunctionMock('handler3'); - var event = new goog.events.Event('message'); - event.origin = origin; - event.ports = [{'postMessage': postMessage}]; - event.data = { - 'eventId': eventId, - 'eventType': eventType, - 'data': data - }; - // First handler. - postMessage({ - 'status': 'ack', - 'eventId': eventId, - 'eventType': eventType, - 'response': null - }).$once(); - handler1(origin, data).$does(function() { - return expectedResponse1; - }).$once(); - // Second handler. - handler2(origin, data).$does(function() { - return expectedResponse2; - }).$once(); - postMessage({ - 'status': 'done', - 'eventId': eventId, - 'eventType': eventType, - 'response': [{ - 'fulfilled': true, - 'value': expectedResponse1 - }, { - 'fulfilled': true, - 'value': expectedResponse2 - }] - }).$does(function() { - sendCompletionSignal(); - }).$once(); - // Third handler. - handler3().$never(); - mockControl.$replayAll(); - - var receiver = new fireauth.messagechannel.Receiver(eventTarget); - assertTrue(receiver.isListeningTo(eventTarget)); - assertFalse(receiver.isListeningTo(new goog.events.EventTarget())); - receiver.subscribe(eventType, handler1); - receiver.subscribe(eventType, handler2); - receiver.subscribe('unsupportedEvent', handler3); - // Simulate incoming sender message event. handler1 and handler2 should - // trigger. - eventTarget.dispatchEvent(event); - receiver.unsubscribe(eventType, handler1); - receiver.unsubscribe(eventType, handler2); - // Second event should not have any effect. - eventTarget.dispatchEvent(event); - - return new goog.Promise(function(resolve, reject) { - sendCompletionSignal = resolve; - }); -} - - -function testReceiver_multipleHandler_singleEvent_error() { - var sendCompletionSignal; - var eventType = 'eventType1'; - var eventId = '1234'; - var origin = 'https://www.example.com'; - var data = { - 'a': 1, - 'b': 2 - }; - var expectedResponse1 = { - 'c': 3 - }; - var expectedError = new Error('some error occurred'); - var eventTarget = new goog.events.EventTarget(); - var postMessage = mockControl.createFunctionMock('postMessage'); - var handler1 = mockControl.createFunctionMock('handler1'); - var handler2 = mockControl.createFunctionMock('handler2'); - var handler3 = mockControl.createFunctionMock('handler3'); - var event = new goog.events.Event('message'); - event.origin = origin; - event.ports = [{'postMessage': postMessage}]; - event.data = { - 'eventId': eventId, - 'eventType': eventType, - 'data': data - }; - // First handler. - postMessage({ - 'status': 'ack', - 'eventId': eventId, - 'eventType': eventType, - 'response': null - }).$once(); - handler1(origin, data).$does(function() { - return expectedResponse1; - }).$once(); - // Second handler. - handler2(origin, data).$does(function() { - throw expectedError; - }).$once(); - postMessage({ - 'status': 'done', - 'eventId': eventId, - 'eventType': eventType, - 'response': [{ - 'fulfilled': true, - 'value': expectedResponse1 - }, { - 'fulfilled': false, - 'reason': expectedError.message - }] - }).$does(function() { - sendCompletionSignal(); - }).$once(); - // Third handler. - handler3().$never(); - mockControl.$replayAll(); - - var receiver = new fireauth.messagechannel.Receiver(eventTarget); - assertTrue(receiver.isListeningTo(eventTarget)); - assertFalse(receiver.isListeningTo(new goog.events.EventTarget())); - receiver.subscribe(eventType, handler1); - receiver.subscribe(eventType, handler2); - receiver.subscribe('unsupportedEvent', handler3); - // Simulate incoming sender message event. handler1 and handler2 should - // trigger. - eventTarget.dispatchEvent(event); - receiver.unsubscribe(eventType, handler1); - receiver.unsubscribe(eventType, handler2); - // Second event should not have any effect. - eventTarget.dispatchEvent(event); - - return new goog.Promise(function(resolve, reject) { - sendCompletionSignal = resolve; - }); -} - - -function testReceiver_multipleHandler_multipleEvent_success() { - var sendCompletionSignal; - var eventType1 = 'eventType1'; - var eventId1 = '1234'; - var origin1 = 'https://www.example.com'; - var data1 = { - 'a': 1, - 'b': 2 - }; - var expectedResponse1 = { - 'response': 'res1' - }; - var eventType2 = 'eventType2'; - var eventId2 = '5678'; - var origin2 = 'https://www.other.com'; - var data2 = { - 'c': 3, - 'd': 4 - }; - var expectedResponse2 = { - 'response': 'res2' - }; - var eventTarget = new goog.events.EventTarget(); - var postMessage = mockControl.createFunctionMock('postMessage'); - var handler1 = mockControl.createFunctionMock('handler1'); - var handler2 = mockControl.createFunctionMock('handler2'); - var handler3 = mockControl.createFunctionMock('handler3'); - var event1 = new goog.events.Event('message'); - event1.origin = origin1; - event1.ports = [{'postMessage': postMessage}]; - event1.data = { - 'eventId': eventId1, - 'eventType': eventType1, - 'data': data1 - }; - var event2 = new goog.events.Event('message'); - event2.origin = origin2; - event2.ports = [{'postMessage': postMessage}]; - event2.data = { - 'eventId': eventId2, - 'eventType': eventType2, - 'data': data2 - }; - // First handler. - postMessage({ - 'status': 'ack', - 'eventId': eventId1, - 'eventType': eventType1, - 'response': null - }).$once(); - postMessage({ - 'status': 'ack', - 'eventId': eventId2, - 'eventType': eventType2, - 'response': null - }).$once(); - handler1(origin1, data1).$does(function() { - return expectedResponse1; - }).$once(); - // Second handler. - handler2(origin2, data2).$does(function() { - return expectedResponse2; - }).$once(); - postMessage({ - 'status': 'done', - 'eventId': eventId1, - 'eventType': eventType1, - 'response': [{ - 'fulfilled': true, - 'value': expectedResponse1 - }] - }).$once(); - postMessage({ - 'status': 'done', - 'eventId': eventId2, - 'eventType': eventType2, - 'response': [{ - 'fulfilled': true, - 'value': expectedResponse2 - }] - }).$does(function() { - sendCompletionSignal(); - }).$once(); - // Third handler. - handler3().$never(); - mockControl.$replayAll(); - - var receiver = new fireauth.messagechannel.Receiver(eventTarget); - assertTrue(receiver.isListeningTo(eventTarget)); - assertFalse(receiver.isListeningTo(new goog.events.EventTarget())); - // Subscribe, unsubscribe and then re-subscribe handler1. - receiver.subscribe(eventType1, handler1); - receiver.unsubscribe(eventType1, handler1); - receiver.subscribe(eventType1, handler1); - - receiver.subscribe(eventType2, handler2); - receiver.subscribe('unsupportedEvent', handler3); - // Simulate 2 incoming sender message events (eventType 1 and 2). handler1 and - // handler2 should trigger. - eventTarget.dispatchEvent(event1); - eventTarget.dispatchEvent(event2); - - return new goog.Promise(function(resolve, reject) { - sendCompletionSignal = resolve; - }); -} - - -function testReceiver_singleHandler_unsubscribeAll() { - var eventType = 'eventType1'; - var eventId = '1234'; - var origin = 'https://www.example.com'; - var data = { - 'a': 1, - 'b': 2 - }; - var eventTarget = new goog.events.EventTarget(); - var postMessage = mockControl.createFunctionMock('postMessage'); - var handler1 = mockControl.createFunctionMock('handler1'); - var handler2 = mockControl.createFunctionMock('handler2'); - var event = new goog.events.Event('message'); - event.origin = origin; - event.ports = [{'postMessage': postMessage}]; - event.data = { - 'eventId': eventId, - 'eventType': eventType, - 'data': data - }; - postMessage(ignoreArgument).$never(); - handler1().$never(); - handler2().$never(); - mockControl.$replayAll(); - - var receiver = new fireauth.messagechannel.Receiver(eventTarget); - assertTrue(receiver.isListeningTo(eventTarget)); - assertFalse(receiver.isListeningTo(new goog.events.EventTarget())); - receiver.subscribe(eventType, handler1); - receiver.subscribe(eventType, handler2); - // Unsubscribe all handlers. - receiver.unsubscribe(eventType); - // No handler should be triggered. - eventTarget.dispatchEvent(event); -} diff --git a/packages/auth/test/messagechannel/sender_test.js b/packages/auth/test/messagechannel/sender_test.js deleted file mode 100644 index 8b5957fe8fa..00000000000 --- a/packages/auth/test/messagechannel/sender_test.js +++ /dev/null @@ -1,660 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for sender.js. - */ - -goog.provide('fireauth.messagechannel.SenderTest'); - -goog.require('fireauth.messagechannel.Error'); -goog.require('fireauth.messagechannel.Sender'); -goog.require('fireauth.messagechannel.TimeoutDuration'); -goog.require('fireauth.messagechannel.utils'); -goog.require('goog.Promise'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventTarget'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.messagechannel.SenderTest'); - - -var mockControl; -var clock; -var ignoreArgument; - - -function setUp() { - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); - // Install mock clock. - clock = new goog.testing.MockClock(true); -} - - -function tearDown() { - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } - goog.dispose(clock); -} - - -/** - * @param {!mockControl} mockControl The MockControl reference. - * @return {!MessageChannel} A mock messagechannel for testing. - */ -function createMockMessageChannel(mockControl) { - var messageChannel = { - port1: new goog.events.EventTarget(), - port2: new goog.events.EventTarget() - }; - messageChannel.port1.start = mockControl.createFunctionMock('start'); - messageChannel.port1.close = mockControl.createFunctionMock('close'); - return messageChannel; -} - - -function testSender_messageChannelUnsupported() { - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - initializeMessageChannel().$returns(null); - postMessage(ignoreArgument, ignoreArgument, ignoreArgument).$never(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send('myEvent', {'a': 1, 'b': 2}) - .then(fail) - .thenCatch(function(error) { - assertEquals( - fireauth.messagechannel.Error.CONNECTION_UNAVAILABLE, - error.message); - }); -} - - -function testSender_send_ackTimeout() { - var eventId = '12345678'; - var eventType = 'myEvent'; - var data = {'a': 1, 'b': 2}; - var expectedRequest = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - - var messageChannel = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - generateEventId().$returns(eventId).$once(); - messageChannel.port1.start().$once(); - postMessage(expectedRequest, [messageChannel.port2]).$does(function() { - // Simulate ACK timeout. - clock.tick(fireauth.messagechannel.TimeoutDuration.ACK); - }).$once(); - messageChannel.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send(eventType, data) - .then(fail) - .thenCatch(function(error) { - assertEquals( - fireauth.messagechannel.Error.UNSUPPORTED_EVENT, - error.message); - }); -} - - -function testSender_send_ackTimeout_longTimeout() { - var eventId = '12345678'; - var eventType = 'myEvent'; - var data = {'a': 1, 'b': 2}; - var expectedRequest = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - - var messageChannel = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - generateEventId().$returns(eventId).$once(); - messageChannel.port1.start().$once(); - postMessage(expectedRequest, [messageChannel.port2]).$does(function() { - // Simulate long ACK timeout. - clock.tick(fireauth.messagechannel.TimeoutDuration.LONG_ACK); - }).$once(); - messageChannel.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send(eventType, data, true) - .then(fail) - .thenCatch(function(error) { - assertEquals( - fireauth.messagechannel.Error.UNSUPPORTED_EVENT, - error.message); - }); -} - - -function testSender_send_invalidResponse() { - var eventId = '12345678'; - var eventType = 'myEvent'; - var data = {'a': 1, 'b': 2}; - var expectedRequest = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - - var messageChannel = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - generateEventId().$returns(eventId).$once(); - messageChannel.port1.start().$once(); - postMessage(expectedRequest, [messageChannel.port2]).$does(function() { - var event = new goog.events.Event('message'); - // Simulate an invalid response. - event.data = { - 'eventType': eventType, - 'eventId': eventId, - 'status': 'invalid' - }; - messageChannel.port1.dispatchEvent(event); - }).$once(); - messageChannel.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send(eventType, data) - .then(fail) - .thenCatch(function(error) { - assertEquals( - fireauth.messagechannel.Error.INVALID_RESPONSE, - error.message); - }); -} - - -function testSender_send_completionTimeout() { - var eventId = '12345678'; - var eventType = 'myEvent'; - var data = {'a': 1, 'b': 2}; - var expectedRequest = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - - var messageChannel = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - generateEventId().$returns(eventId).$once(); - messageChannel.port1.start().$once(); - postMessage(expectedRequest, [messageChannel.port2]).$does(function() { - var event = new goog.events.Event('message'); - event.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'ack' - }; - // Simulate ACK response sent successfully. - messageChannel.port1.dispatchEvent(event); - // Simulate completion response timing out. - clock.tick(fireauth.messagechannel.TimeoutDuration.COMPLETION); - }).$once(); - messageChannel.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send(eventType, data) - .then(fail) - .thenCatch(function(error) { - assertEquals( - fireauth.messagechannel.Error.TIMEOUT, - error.message); - }); -} - - -function testSender_send_completionSuccess() { - var eventId = '12345678'; - var eventType = 'myEvent'; - var data = {'a': 1, 'b': 2}; - var expectedRequest = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - var expectedSuccessResponse = [{ - 'fulfilled': true, - 'value': { - 'c': 3, - 'd': 4 - } - }]; - - var messageChannel = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - generateEventId().$returns(eventId).$once(); - messageChannel.port1.start().$once(); - postMessage(expectedRequest, [messageChannel.port2]).$does(function() { - var event1 = new goog.events.Event('message'); - var event2 = new goog.events.Event('message'); - event1.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'ack' - }; - event2.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'done', - 'response': expectedSuccessResponse - }; - // Simulate successful ACK and DONE responses. - messageChannel.port1.dispatchEvent(event1); - messageChannel.port1.dispatchEvent(event2); - }).$once(); - messageChannel.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send(eventType, data) - .then(function(result) { - assertEquals(expectedSuccessResponse, result); - sender.close(); - // Second call should do nothing. - sender.close(); - // Connection should not be available anymore. - return sender.send(eventType, data).then(fail, function(error) { - assertEquals( - fireauth.messagechannel.Error.CONNECTION_UNAVAILABLE, - error.message); - }); - }); -} - - -function testSender_send_completionSuccess_longTimeout() { - var eventId = '12345678'; - var eventType = 'myEvent'; - var data = {'a': 1, 'b': 2}; - var expectedRequest = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - var expectedSuccessResponse = [{ - 'fulfilled': true, - 'value': { - 'c': 3, - 'd': 4 - } - }]; - - var messageChannel = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - generateEventId().$returns(eventId).$once(); - messageChannel.port1.start().$once(); - postMessage(expectedRequest, [messageChannel.port2]).$does(function() { - var event1 = new goog.events.Event('message'); - var event2 = new goog.events.Event('message'); - event1.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'ack' - }; - event2.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'done', - 'response': expectedSuccessResponse - }; - // Simulate successful ACK and DONE responses. - // Long timeout should be applied. - clock.tick(fireauth.messagechannel.TimeoutDuration.LONG_ACK - 1); - messageChannel.port1.dispatchEvent(event1); - clock.tick(fireauth.messagechannel.TimeoutDuration.COMPLETION - 1); - messageChannel.port1.dispatchEvent(event2); - }).$once(); - messageChannel.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send(eventType, data, true) - .then(function(result) { - assertEquals(expectedSuccessResponse, result); - sender.close(); - // Second call should do nothing. - sender.close(); - // Connection should not be available anymore. - return sender.send(eventType, data).then(fail, function(error) { - assertEquals( - fireauth.messagechannel.Error.CONNECTION_UNAVAILABLE, - error.message); - }); - }); -} - - -function testSender_send_completionError() { - var eventId = '12345678'; - var eventType = 'myEvent'; - var data = {'a': 1, 'b': 2}; - var expectedRequest = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - var expectedErrorResponse = [{ - 'fulfilled': false, - 'reason': 'some error occurred' - }]; - - var messageChannel = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - // start called early. - messageChannel.port1.start().$once(); - generateEventId().$returns(eventId).$once(); - postMessage(expectedRequest, [messageChannel.port2]).$does(function() { - var event1 = new goog.events.Event('message'); - var event2 = new goog.events.Event('message'); - event1.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'ack' - }; - event2.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'done', - 'response': expectedErrorResponse - }; - clock.tick(fireauth.messagechannel.TimeoutDuration.ACK - 1); - messageChannel.port1.dispatchEvent(event1); - clock.tick(fireauth.messagechannel.TimeoutDuration.COMPLETION - 1); - messageChannel.port1.dispatchEvent(event2); - }).$once(); - messageChannel.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send(eventType, data) - .then(function(result) { - assertEquals(expectedErrorResponse, result); - }); -} - - -function testSender_send_closedConnection() { - var eventId = '12345678'; - var eventType = 'myEvent'; - var data = {'a': 1, 'b': 2}; - var expectedRequest = { - 'eventType': eventType, - 'eventId': eventId, - 'data': data - }; - var expectedErrorResponse = { - 'fulfilled': true, - 'value': 'success' - }; - - var messageChannel = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - // start called early. - messageChannel.port1.start().$once(); - generateEventId().$returns(eventId).$once(); - postMessage(expectedRequest, [messageChannel.port2]).$does(function() { - var event1 = new goog.events.Event('message'); - var event2 = new goog.events.Event('message'); - event1.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'ack' - }; - event2.data = { - 'eventId': eventId, - 'eventType': eventType, - 'status': 'done', - 'response': expectedErrorResponse - }; - messageChannel.port1.dispatchEvent(event1); - // Simulate the connection gets closed. - sender.close(); - messageChannel.port1.dispatchEvent(event2); - // Second event will be ignored and the resolver will timeout. - clock.tick(fireauth.messagechannel.TimeoutDuration.COMPLETION); - }).$once(); - messageChannel.port1.close().$once(); - messageChannel.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - return sender.send(eventType, data) - .then(fail, function(error) { - assertEquals( - fireauth.messagechannel.Error.TIMEOUT, - error.message); - }); -} - - -function testSender_send_multipleMessages() { - var eventId1 = '1234'; - var eventType1 = 'myEvent1'; - var data1 = {'request1': 'req1'}; - var expectedRequest1 = { - 'eventType': eventType1, - 'eventId': eventId1, - 'data': data1 - }; - var expectedSuccessResponse1 = [{ - 'fulfilled': true, - 'value': { - 'response': 'res1' - } - }]; - - var eventId2 = '5678'; - var eventType2 = 'myEvent2'; - var data2 = {'request2': 'req2'}; - var expectedRequest2 = { - 'eventType': eventType2, - 'eventId': eventId2, - 'data': data2 - }; - var expectedSuccessResponse2 = [{ - 'fulfilled': true, - 'value': { - 'response': 'res2' - } - }]; - - var eventId3 = '9012'; - var data3 = {'request3': 'req3'}; - var expectedRequest3 = { - // Test with same event as above. - 'eventType': eventType1, - 'eventId': eventId3, - 'data': data3 - }; - var expectedSuccessResponse3 = [{ - 'fulfilled': true, - 'value': { - 'response': 'res3' - } - }]; - - var messageChannel = createMockMessageChannel(mockControl); - var messageChannel2 = createMockMessageChannel(mockControl); - var messageChannel3 = createMockMessageChannel(mockControl); - var postMessage = mockControl.createFunctionMock('postMessage'); - var initializeMessageChannel = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'initializeMessageChannel'); - var generateEventId = mockControl.createMethodMock( - fireauth.messagechannel.utils, 'generateEventId'); - initializeMessageChannel().$returns(messageChannel).$once(); - initializeMessageChannel().$returns(messageChannel2).$once(); - initializeMessageChannel().$returns(messageChannel3).$once(); - generateEventId().$returns(eventId1).$once(); - messageChannel.port1.start().$once(); - generateEventId().$returns(eventId2).$once(); - messageChannel2.port1.start().$once(); - generateEventId().$returns(eventId3).$once(); - messageChannel3.port1.start().$once(); - // First event. - postMessage(expectedRequest1, [messageChannel.port2]).$does(function() { - var event1 = new goog.events.Event('message'); - var event2 = new goog.events.Event('message'); - event1.data = { - 'eventId': eventId1, - 'eventType': eventType1, - 'status': 'ack' - }; - event2.data = { - 'eventId': eventId1, - 'eventType': eventType1, - 'status': 'done', - 'response': expectedSuccessResponse1 - }; - messageChannel.port1.dispatchEvent(event1); - messageChannel.port1.dispatchEvent(event2); - }).$once(); - // Second event. - postMessage(expectedRequest2, [messageChannel2.port2]).$does(function() { - var event1 = new goog.events.Event('message'); - var event2 = new goog.events.Event('message'); - event1.data = { - 'eventId': eventId2, - 'eventType': eventType2, - 'status': 'ack' - }; - event2.data = { - 'eventId': eventId2, - 'eventType': eventType2, - 'status': 'done', - 'response': expectedSuccessResponse2 - }; - messageChannel2.port1.dispatchEvent(event1); - messageChannel2.port1.dispatchEvent(event2); - }).$once(); - // Third event. - postMessage(expectedRequest3, [messageChannel3.port2]).$does(function() { - var event1 = new goog.events.Event('message'); - var event2 = new goog.events.Event('message'); - event1.data = { - 'eventId': eventId3, - 'eventType': eventType1, - 'status': 'ack' - }; - event2.data = { - 'eventId': eventId3, - 'eventType': eventType1, - 'status': 'done', - 'response': expectedSuccessResponse3 - }; - messageChannel3.port1.dispatchEvent(event1); - messageChannel3.port1.dispatchEvent(event2); - }).$once(); - messageChannel.port1.close().$once(); - messageChannel2.port1.close().$once(); - messageChannel3.port1.close().$once(); - mockControl.$replayAll(); - - var sender = new fireauth.messagechannel.Sender({ - 'postMessage': postMessage - }); - // goog.Promise.all will fail if a single promise fails. - // Test with mixture of events (2 types of events). Each request will have a - // unique event ID. - return goog.Promise.all( - [ - sender.send(eventType1, data1), - sender.send(eventType2, data2), - sender.send(eventType1, data3) - ]).then(function(results) { - assertEquals(3, results.length); - assertEquals(expectedSuccessResponse1, results[0]); - assertEquals(expectedSuccessResponse2, results[1]); - assertEquals(expectedSuccessResponse3, results[2]); - }); -} - diff --git a/packages/auth/test/multifactorassertion_test.js b/packages/auth/test/multifactorassertion_test.js deleted file mode 100644 index 7df9d5ff09f..00000000000 --- a/packages/auth/test/multifactorassertion_test.js +++ /dev/null @@ -1,334 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for multifactorassertion.js - */ - -goog.provide('fireauth.MultiFactorAssertionTest'); - -goog.require('fireauth.AuthCredentialMultiFactorAssertion'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.MultiFactorAuthCredential'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.PhoneMultiFactorAssertion'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('goog.Promise'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.MultiFactorAssertionTest'); - - -var mockControl; -var jwt = fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password' - } -}); -var pendingCredential = 'MFA_PENDING_CREDENTIAL'; -var verificationId = 'SESSION_INFO'; -var verificationCode = '123456'; -var factorDisplayName = 'Work phone number'; -var enrollSession; -var signInSession; -var phoneAuthCredential; -var successTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }), - 'refreshToken': 'REFRESH_TOKEN' -}; - -function setUp() { - phoneAuthCredential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - enrollSession = new fireauth.MultiFactorSession(jwt); - signInSession = new fireauth.MultiFactorSession(null, pendingCredential); - mockControl = new goog.testing.MockControl(); -} - - -function tearDown() { - enrollSession = null; - signInSession = null; - mockControl.$verifyAll(); - mockControl.$tearDown(); -} - - -function testAuthCredentialMultiFactorAssertion_process_enrollSuccess() { - var expectedEnrollRequestIdentifier = { - 'idToken': jwt, - 'displayName': factorDisplayName - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var authCredential = mockControl.createStrictMock( - fireauth.MultiFactorAuthCredential); - authCredential['providerId'] = 'PROVIDER_ID'; - authCredential.finalizeMfaEnrollment( - rpcHandler, expectedEnrollRequestIdentifier).$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - - mockControl.$replayAll(); - - var assertion = - new fireauth.AuthCredentialMultiFactorAssertion(authCredential); - - assertEquals(authCredential['providerId'], assertion['factorId']); - return assertion.process(rpcHandler, enrollSession, factorDisplayName) - .then(function(result) { - assertObjectEquals(successTokenResponse, result); - }); -} - - -function testAuthCredentialMultiFactorAssertion_process_enrollSuccess_noName() { - var expectedEnrollRequestIdentifier = { - 'idToken': jwt - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var authCredential = mockControl.createStrictMock( - fireauth.MultiFactorAuthCredential); - authCredential['providerId'] = 'PROVIDER_ID'; - authCredential.finalizeMfaEnrollment( - rpcHandler, expectedEnrollRequestIdentifier).$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - - mockControl.$replayAll(); - - var assertion = - new fireauth.AuthCredentialMultiFactorAssertion(authCredential); - - assertEquals(authCredential['providerId'], assertion['factorId']); - return assertion.process(rpcHandler, enrollSession) - .then(function(result) { - assertObjectEquals(successTokenResponse, result); - }); -} - - -function testAuthCredentialMultiFactorAssertion_process_enrollError() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CREDENTIAL_TOO_OLD_LOGIN_AGAIN); - var expectedEnrollRequestIdentifier = { - 'idToken': jwt, - 'displayName': factorDisplayName - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var authCredential = mockControl.createStrictMock( - fireauth.MultiFactorAuthCredential); - authCredential['providerId'] = 'PROVIDER_ID'; - authCredential.finalizeMfaEnrollment( - rpcHandler, expectedEnrollRequestIdentifier).$once() - .$returns(goog.Promise.reject(expectedError)); - - mockControl.$replayAll(); - - var assertion = - new fireauth.AuthCredentialMultiFactorAssertion(authCredential); - - assertEquals(authCredential['providerId'], assertion['factorId']); - return assertion.process(rpcHandler, enrollSession, factorDisplayName) - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testAuthCredentialMultiFactorAssertion_process_signInSuccess() { - var expectedSignInRequestIdentifier = { - 'mfaPendingCredential': pendingCredential - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var authCredential = mockControl.createStrictMock( - fireauth.MultiFactorAuthCredential); - authCredential['providerId'] = 'PROVIDER_ID'; - authCredential.finalizeMfaSignIn( - rpcHandler, expectedSignInRequestIdentifier).$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - - mockControl.$replayAll(); - - var assertion = - new fireauth.AuthCredentialMultiFactorAssertion(authCredential); - - assertEquals(authCredential['providerId'], assertion['factorId']); - return assertion.process(rpcHandler, signInSession) - .then(function(result) { - assertObjectEquals(successTokenResponse, result); - }); -} - - -function testAuthCredentialMultiFactorAssertion_process_signInError() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CODE_EXPIRED); - var expectedSignInRequestIdentifier = { - 'mfaPendingCredential': pendingCredential - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var authCredential = mockControl.createStrictMock( - fireauth.MultiFactorAuthCredential); - authCredential['providerId'] = 'PROVIDER_ID'; - authCredential.finalizeMfaSignIn( - rpcHandler, expectedSignInRequestIdentifier).$once() - .$returns(goog.Promise.reject(expectedError)); - - mockControl.$replayAll(); - - var assertion = - new fireauth.AuthCredentialMultiFactorAssertion(authCredential); - - assertEquals(authCredential['providerId'], assertion['factorId']); - return assertion.process(rpcHandler, signInSession) - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testPhoneMultiFactorAssertion_invalid() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'firebase.auth.PhoneMultiFactorAssertion requires a valid ' + - 'firebase.auth.PhoneAuthCredential'); - var authCredential = mockControl.createStrictMock( - fireauth.MultiFactorAuthCredential); - // Simulate non-phone AuthCredential. - authCredential['providerId'] = 'PROVIDER_ID'; - - var error = assertThrows(function() { - return new fireauth.PhoneMultiFactorAssertion(authCredential); - }); - - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); -} - - -function testPhoneMultiFactorAssertion_process_enrollSuccess() { - var expectedEnrollRequest = { - 'idToken': jwt, - 'displayName': factorDisplayName, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaEnrollment(expectedEnrollRequest).$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - - mockControl.$replayAll(); - - var assertion = new fireauth.PhoneMultiFactorAssertion(phoneAuthCredential); - - assertEquals('phone', assertion['factorId']); - return assertion.process(rpcHandler, enrollSession, factorDisplayName) - .then(function(result) { - assertObjectEquals(successTokenResponse, result); - }); -} - - -function testPhoneMultiFactorAssertion_process_enrollError() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CREDENTIAL_TOO_OLD_LOGIN_AGAIN); - var expectedEnrollRequest = { - 'idToken': jwt, - 'displayName': factorDisplayName, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaEnrollment(expectedEnrollRequest).$once() - .$returns(goog.Promise.reject(expectedError)); - - mockControl.$replayAll(); - - var assertion = new fireauth.PhoneMultiFactorAssertion(phoneAuthCredential); - - assertEquals('phone', assertion['factorId']); - return assertion.process(rpcHandler, enrollSession, factorDisplayName) - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testPhoneMultiFactorAssertion_process_signInSuccess() { - var expectedSignInRequest = { - 'mfaPendingCredential': pendingCredential, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaSignIn(expectedSignInRequest).$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - - mockControl.$replayAll(); - - var assertion = new fireauth.PhoneMultiFactorAssertion(phoneAuthCredential); - - assertEquals('phone', assertion['factorId']); - return assertion.process(rpcHandler, signInSession) - .then(function(result) { - assertObjectEquals(successTokenResponse, result); - }); -} - - -function testPhoneMultiFactorAssertion_process_signInError() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CODE_EXPIRED); - var expectedSignInRequest = { - 'mfaPendingCredential': pendingCredential, - 'phoneVerificationInfo': { - 'sessionInfo': verificationId, - 'code': verificationCode - } - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - rpcHandler.finalizePhoneMfaSignIn(expectedSignInRequest).$once() - .$returns(goog.Promise.reject(expectedError)); - - mockControl.$replayAll(); - - var assertion = new fireauth.PhoneMultiFactorAssertion(phoneAuthCredential); - - assertEquals('phone', assertion['factorId']); - return assertion.process(rpcHandler, signInSession) - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} diff --git a/packages/auth/test/multifactorerror_test.js b/packages/auth/test/multifactorerror_test.js deleted file mode 100644 index 5452d4c83a0..00000000000 --- a/packages/auth/test/multifactorerror_test.js +++ /dev/null @@ -1,229 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for multifactorerror.js - */ - -goog.provide('fireauth.MultiFactorErrorTest'); - -goog.require('fireauth.Auth'); -goog.require('fireauth.MultiFactorError'); -goog.require('fireauth.MultiFactorResolver'); -goog.require('fireauth.authenum.Error'); -goog.require('goog.Promise'); -goog.require('goog.object'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.MultiFactorErrorTest'); - - -var app = firebase.initializeApp({ - apiKey: 'myApiKey' -}); -var auth = new fireauth.Auth(app); -var enrollmentList; -var pendingCredential; -var serverResponse; -var serverResponseWithOtherInfo; -var mockUserCredential; -var onIdTokenResolver; -var now = new Date(); - - -function setUp() { - pendingCredential = 'PENDING_CREDENTIAL'; - enrollmentList = [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+*******1234' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+*******6789' - } - ]; - serverResponse = { - 'mfaInfo': enrollmentList, - 'mfaPendingCredential': pendingCredential - }; - serverResponseWithOtherInfo = goog.object.clone(serverResponse); - goog.object.extend(serverResponseWithOtherInfo, { - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + - '"name":{"givenName":"John","familyName":"Doe"}}' - }); - mockUserCredential = { - 'user': 'User', - 'credential': 'Credential' - }; - onIdTokenResolver = function(signInResponse) { - return goog.Promise.resolve(mockUserCredential); - }; -} - - -function tearDown() { - onIdTokenResolver = null; - mockUserCredential = null; - enrollmentList = null; - pendingCredential = null; -} - - -function testMultiFactorError_defaultMessage() { - var expectedResolver = - new fireauth.MultiFactorResolver(auth, serverResponse, onIdTokenResolver); - - var error = new fireauth.MultiFactorError( - auth, serverResponse, onIdTokenResolver); - - assertEquals('auth/' + fireauth.authenum.Error.MFA_REQUIRED, error.code); - assertEquals( - 'Proof of ownership of a second factor is required to complete sign-in.', - error.message); - assertObjectEquals(serverResponse, error.serverResponse); - assertObjectEquals(expectedResolver, error.resolver); -} - - -function testMultiFactorError_customMessage() { - var expectedResolver = new fireauth.MultiFactorResolver( - auth, serverResponseWithOtherInfo, onIdTokenResolver); - - var error = new fireauth.MultiFactorError( - auth, serverResponseWithOtherInfo, onIdTokenResolver, 'custom message'); - - assertEquals('auth/' + fireauth.authenum.Error.MFA_REQUIRED, error.code); - assertEquals( - 'custom message', - error.message); - assertObjectEquals(serverResponseWithOtherInfo, error.serverResponse); - assertObjectEquals(expectedResolver, error.resolver); -} - - -function testMultiFactorError_toPlainObject() { - var errorWithDefaultMessage = new fireauth.MultiFactorError( - auth, serverResponse, onIdTokenResolver); - var errorWithCustomMessage = new fireauth.MultiFactorError( - auth, serverResponse, onIdTokenResolver, 'custom message'); - var errorWithOtherInfo = new fireauth.MultiFactorError( - auth, serverResponseWithOtherInfo, onIdTokenResolver); - - assertObjectEquals( - { - 'code': 'auth/' + fireauth.authenum.Error.MFA_REQUIRED, - 'message': 'Proof of ownership of a second factor is required to ' + - 'complete sign-in.', - 'serverResponse': serverResponse - }, - errorWithDefaultMessage.toPlainObject()); - assertObjectEquals( - { - 'code': 'auth/' + fireauth.authenum.Error.MFA_REQUIRED, - 'message': 'custom message', - 'serverResponse': serverResponse - }, - errorWithCustomMessage.toPlainObject()); - assertObjectEquals( - { - 'code': 'auth/' + fireauth.authenum.Error.MFA_REQUIRED, - 'message': 'Proof of ownership of a second factor is required to ' + - 'complete sign-in.', - 'serverResponse': serverResponseWithOtherInfo - }, - errorWithOtherInfo.toPlainObject()); -} - - -function testMultiFactorError_fromPlainObject() { - var customMessage = 'custom message'; - var expectedError = new fireauth.MultiFactorError( - auth, serverResponse, onIdTokenResolver); - var expectedErrorWithMessage = new fireauth.MultiFactorError( - auth, serverResponse, onIdTokenResolver, customMessage); - var responseWithCredential = { - 'serverResponse': goog.object.clone(serverResponse) - }; - responseWithCredential['code'] = 'auth/multi-factor-auth-required'; - var responseWithoutCredential = { - 'serverResponse': goog.object.clone(serverResponse) - }; - delete responseWithoutCredential['serverResponse']['mfaPendingCredential']; - responseWithoutCredential['code'] = 'auth/multi-factor-auth-required'; - var responseWithCredentialAndMessage = - goog.object.clone(responseWithCredential); - responseWithCredentialAndMessage['message'] = customMessage; - var responseWithIncorrectCode = goog.object.clone(responseWithCredential); - responseWithIncorrectCode['code'] = 'auth/internal-error'; - var errorWithOtherInfo = new fireauth.MultiFactorError( - auth, serverResponseWithOtherInfo, onIdTokenResolver); - - // null response. - assertNull( - fireauth.MultiFactorError.fromPlainObject(null, auth, onIdTokenResolver)); - // Empty object response. - assertNull( - fireauth.MultiFactorError.fromPlainObject({}, auth, onIdTokenResolver)); - // undefined object response. - assertNull( - fireauth.MultiFactorError.fromPlainObject( - undefined, auth, onIdTokenResolver)); - // Response with just code. - assertNull( - fireauth.MultiFactorError.fromPlainObject( - {'code': 'auth/multi-factor-auth-required'}, - auth, onIdTokenResolver)); - // Response with code and no credential. - assertNull( - fireauth.MultiFactorError.fromPlainObject( - responseWithoutCredential, auth, onIdTokenResolver)); - // Response with credential but incorrect code. - assertNull( - fireauth.MultiFactorError.fromPlainObject( - responseWithIncorrectCode, auth, onIdTokenResolver)); - - // Valid response with default message. - assertObjectEquals( - expectedError, - fireauth.MultiFactorError.fromPlainObject( - responseWithCredential, auth, onIdTokenResolver)); - // Valid response with custom message. - assertObjectEquals( - expectedErrorWithMessage, - fireauth.MultiFactorError.fromPlainObject( - responseWithCredentialAndMessage, auth, onIdTokenResolver)); - // Using toPlainObject representation. - assertObjectEquals( - expectedError, - fireauth.MultiFactorError.fromPlainObject( - expectedError.toPlainObject(), auth, onIdTokenResolver)); - // Using toPlainObject representation with other info in server response. - assertObjectEquals( - errorWithOtherInfo, - fireauth.MultiFactorError.fromPlainObject( - errorWithOtherInfo.toPlainObject(), auth, onIdTokenResolver)); -} diff --git a/packages/auth/test/multifactorgenerator_test.js b/packages/auth/test/multifactorgenerator_test.js deleted file mode 100644 index 943b328941e..00000000000 --- a/packages/auth/test/multifactorgenerator_test.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for multifactorgenerator.js - */ - -goog.provide('fireauth.MultiFactorGeneratorTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.EmailAuthProvider'); -goog.require('fireauth.PhoneAuthProvider'); -goog.require('fireauth.PhoneMultiFactorAssertion'); -goog.require('fireauth.PhoneMultiFactorGenerator'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.MultiFactorGeneratorTest'); - - -var verificationId = 'SESSION_INFO'; -var verificationCode = '123456'; - - -function testPhoneMultiFactorGenerator() { - assertEquals( - fireauth.constants.SecondFactorType.PHONE, - fireauth.PhoneMultiFactorGenerator.FACTOR_ID); -} - - -function testPhoneMultiFactorGenerator_assertion_success() { - var phoneAuthCredential = fireauth.PhoneAuthProvider.credential( - verificationId, verificationCode); - var assertion = - fireauth.PhoneMultiFactorGenerator.assertion(phoneAuthCredential); - - assertTrue(assertion instanceof fireauth.PhoneMultiFactorAssertion); - assertObjectEquals( - new fireauth.PhoneMultiFactorAssertion(phoneAuthCredential), - assertion); -} - - -function testPhoneMultiFactorGenerator_assertion_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'firebase.auth.PhoneMultiFactorAssertion requires a valid ' + - 'firebase.auth.PhoneAuthCredential'); - var authCredential = fireauth.EmailAuthProvider.credential( - 'user@example.com', 'password'); - - var error = assertThrows(function() { - return fireauth.PhoneMultiFactorGenerator.assertion(authCredential); - }); - - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); -} diff --git a/packages/auth/test/multifactorinfo_test.js b/packages/auth/test/multifactorinfo_test.js deleted file mode 100644 index c82fe98eb94..00000000000 --- a/packages/auth/test/multifactorinfo_test.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for multifactorinfo.js - */ - -goog.provide('fireauth.MultiFactorInfoTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.PhoneMultiFactorInfo'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.MultiFactorInfoTest'); - - -var now = new Date(); -var phoneServerResponse = { - 'mfaEnrollmentId': 'ENROLLMENT_UID', - 'displayName': 'DISPLAY_NAME', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' -}; - - -function testPhoneMultiFactorInfo_valid() { - var info = new fireauth.PhoneMultiFactorInfo(phoneServerResponse); - - assertEquals(phoneServerResponse['mfaEnrollmentId'], info['uid']); - assertEquals(phoneServerResponse['displayName'], info['displayName']); - assertEquals(fireauth.constants.SecondFactorType.PHONE, info['factorId']); - assertEquals(now.toUTCString(), info['enrollmentTime']); - assertEquals(phoneServerResponse['phoneInfo'], info['phoneNumber']); - - assertObjectEquals( - { - 'uid': phoneServerResponse['mfaEnrollmentId'], - 'displayName': phoneServerResponse['displayName'], - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'enrollmentTime': now.toUTCString(), - 'phoneNumber': phoneServerResponse['phoneInfo'] - }, - info.toPlainObject()); -} - - -function testPhoneMultiFactorInfo_valid_missingFields() { - // Remove all non-required fields. - var serverResponse = { - 'mfaEnrollmentId': phoneServerResponse['mfaEnrollmentId'], - 'phoneInfo': phoneServerResponse['phoneInfo'] - }; - var info = new fireauth.PhoneMultiFactorInfo(serverResponse); - - assertEquals(phoneServerResponse['mfaEnrollmentId'], info['uid']); - assertNull(info['displayName']); - assertEquals(fireauth.constants.SecondFactorType.PHONE, info['factorId']); - assertNull(info['enrollmentTime']); - assertEquals(phoneServerResponse['phoneInfo'], info['phoneNumber']); - - assertObjectEquals( - { - 'uid': phoneServerResponse['mfaEnrollmentId'], - 'displayName': null, - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'enrollmentTime': null, - 'phoneNumber': phoneServerResponse['phoneInfo'] - }, - info.toPlainObject()); -} - - -function testPhoneMultiFactorInfo_invalidMultiFactorInfo() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Internal assert: invalid MultiFactorInfo object'); - - var error = assertThrows(function() { - return new fireauth.PhoneMultiFactorInfo({}); - }); - - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); -} - - -function testPhoneMultiFactorInfo_invalidPhoneMultiFactorInfo() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Internal assert: invalid MultiFactorInfo object'); - - var error = assertThrows(function() { - return new fireauth.PhoneMultiFactorInfo({ - 'mfaEnrollmentId': 'ENROLLMENT_UID' - }); - }); - - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); -} - - -function testMultiFactorInfo_fromServerResponse_phoneMultiFactorInfo() { - var expectedPhoneInfo = - new fireauth.PhoneMultiFactorInfo(phoneServerResponse); - - var info = fireauth.MultiFactorInfo.fromServerResponse(phoneServerResponse); - - assertTrue(info instanceof fireauth.PhoneMultiFactorInfo); - assertObjectEquals(expectedPhoneInfo, info); -} - - -function testMultiFactorInfo_fromServerResponse_invalid() { - assertNull(fireauth.MultiFactorInfo.fromServerResponse(null)); - assertNull(fireauth.MultiFactorInfo.fromServerResponse({})); - assertNull(fireauth.MultiFactorInfo.fromServerResponse({ - 'mfaEnrollmentId': 'ENROLLMENT_UID' - })); -} - - -function testMultiFactorInfo_fromPlainObject_phoneMultiFactorInfo() { - var expectedPhoneInfo = - new fireauth.PhoneMultiFactorInfo(phoneServerResponse); - - var info = fireauth.MultiFactorInfo.fromPlainObject( - expectedPhoneInfo.toPlainObject()); - - assertTrue(info instanceof fireauth.PhoneMultiFactorInfo); - assertObjectEquals(expectedPhoneInfo, info); -} - - -function testMultiFactorInfo_fromPlainObject_invalid() { - assertNull(fireauth.MultiFactorInfo.fromPlainObject(null)); - assertNull(fireauth.MultiFactorInfo.fromPlainObject({})); - assertNull(fireauth.MultiFactorInfo.fromPlainObject({ - 'uid': 'ENROLLMENT_UID' - })); -} diff --git a/packages/auth/test/multifactorresolver_test.js b/packages/auth/test/multifactorresolver_test.js deleted file mode 100644 index 2db8541aa87..00000000000 --- a/packages/auth/test/multifactorresolver_test.js +++ /dev/null @@ -1,241 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for multifactorresolver.js - */ - -goog.provide('fireauth.MultiFactorResolverTest'); - -goog.require('fireauth.Auth'); -goog.require('fireauth.AuthError'); -goog.require('fireauth.MultiFactorAssertion'); -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.MultiFactorResolver'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('goog.Promise'); -goog.require('goog.object'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.MultiFactorResolverTest'); - - -var mockControl; -var app = firebase.initializeApp({ - apiKey: 'myApiKey' -}); -var auth = new fireauth.Auth(app); -var enrollmentList; -var pendingCredential; -var serverResponse; -var serverResponseWithOtherInfo; -var mockUserCredential; -var onIdTokenResolver; -var successTokenResponse; -var now = new Date(); - -function setUp() { - pendingCredential = 'PENDING_CREDENTIAL'; - enrollmentList = [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+*******1234' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+*******6789' - } - ]; - serverResponse = { - 'mfaInfo': enrollmentList, - 'mfaPendingCredential': pendingCredential - }; - serverResponseWithOtherInfo = goog.object.clone(serverResponse); - goog.object.extend(serverResponseWithOtherInfo, { - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + - '"name":{"givenName":"John","familyName":"Doe"}}' - }); - mockUserCredential = { - 'user': 'User', - 'credential': 'Credential' - }; - onIdTokenResolver = function(signInResponse) { - return goog.Promise.resolve(mockUserCredential); - }; - successTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }), - 'refreshToken': 'REFRESH_TOKEN' - }; - mockControl = new goog.testing.MockControl(); -} - - -function tearDown() { - onIdTokenResolver = null; - mockUserCredential = null; - enrollmentList = null; - pendingCredential = null; - successTokenResponse = null; - mockControl.$verifyAll(); - mockControl.$tearDown(); -} - - -function testMultiFactorResolver_valid() { - var expectedHints = [ - fireauth.MultiFactorInfo.fromServerResponse(enrollmentList[0]), - fireauth.MultiFactorInfo.fromServerResponse(enrollmentList[1]) - ]; - var expectedSession = new fireauth.MultiFactorSession( - null, pendingCredential); - - var resolver = assertNotThrows(function() { - return new fireauth.MultiFactorResolver( - auth, serverResponse, onIdTokenResolver); - }); - - assertEquals(auth, resolver.auth); - assertArrayEquals(expectedHints, resolver.hints); - assertObjectEquals(expectedSession, resolver.session); -} - - -function testMultiFactorResolver_invalid() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'Internal assert: Invalid MultiFactorResolver'); - - var error = assertThrows(function() { - // Pending credential must be provided. - return new fireauth.MultiFactorResolver( - auth, {'mfaInfo': enrollmentList}, onIdTokenResolver); - }); - - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); -} - - -function testMultiFactorResolver_resolveSignIn_success() { - var newSignInResponse = goog.object.clone(serverResponseWithOtherInfo); - goog.object.extend(newSignInResponse, successTokenResponse); - delete newSignInResponse['mfaInfo']; - delete newSignInResponse['mfaPendingCredential']; - - var onIdTokenResolver = mockControl.createFunctionMock('onIdTokenResolver'); - var resolver = new fireauth.MultiFactorResolver( - auth, serverResponseWithOtherInfo, onIdTokenResolver); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - - // Simulate assertion processing resolves with the successful token response. - mockAssertion.process(auth.getRpcHandler(), resolver.session) - .$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - // Simulate ID token resolver resolves with the mock UserCredential. - onIdTokenResolver(newSignInResponse) - .$once() - .$returns(goog.Promise.resolve(mockUserCredential)); - - mockControl.$replayAll(); - - return resolver.resolveSignIn(mockAssertion) - .then(function(actualUserCredential) { - assertEquals(mockUserCredential, actualUserCredential); - }); -} - - -function testMultiFactorResolver_resolveSignIn_assertionError() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CODE_EXPIRED); - var onIdTokenResolver = mockControl.createFunctionMock('onIdTokenResolver'); - var resolver = new fireauth.MultiFactorResolver( - auth, serverResponseWithOtherInfo, onIdTokenResolver); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - - // Simulate assertion processing rejects with an error. - mockAssertion.process(auth.getRpcHandler(), resolver.session) - .$once() - .$returns(goog.Promise.reject(expectedError)); - // onIdTokenResolver should not be called. - onIdTokenResolver(goog.testing.mockmatchers.ignoreArgument).$times(0); - - mockControl.$replayAll(); - - return resolver.resolveSignIn(mockAssertion) - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - }); -} - - -function testMultiFactorResolver_resolveSignIn_onIdTokenResolverError() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.USER_MISMATCH); - var newSignInResponse = goog.object.clone(serverResponseWithOtherInfo); - goog.object.extend(newSignInResponse, successTokenResponse); - delete newSignInResponse['mfaInfo']; - delete newSignInResponse['mfaPendingCredential']; - - var onIdTokenResolver = mockControl.createFunctionMock('onIdTokenResolver'); - var resolver = new fireauth.MultiFactorResolver( - auth, serverResponseWithOtherInfo, onIdTokenResolver); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - - // Simulate assertion processing resolves with the successful token response. - mockAssertion.process(auth.getRpcHandler(), resolver.session) - .$once() - .$returns(goog.Promise.resolve(successTokenResponse)); - // Simulate onIdTokenResolver fails with a network error. - onIdTokenResolver(newSignInResponse) - .$once() - .$returns(goog.Promise.reject(expectedError)); - - mockControl.$replayAll(); - - return resolver.resolveSignIn(mockAssertion) - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - }); -} diff --git a/packages/auth/test/multifactorsession_test.js b/packages/auth/test/multifactorsession_test.js deleted file mode 100644 index 6ccf8002436..00000000000 --- a/packages/auth/test/multifactorsession_test.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for multifactorsession.js - */ - -goog.provide('fireauth.MultiFactorSessionTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.MultiFactorSessionTest'); - - -var jwt = fireauth.common.testHelper.createMockJwt(); -var pendingCredential = 'MFA_PENDING_CREDENTIAL'; - - -function testMultiFactorSession_idToken() { - var session = new fireauth.MultiFactorSession(jwt); - - assertEquals( - session.type, fireauth.MultiFactorSession.Type.ENROLL); - assertObjectEquals( - { - 'multiFactorSession': { - 'idToken': jwt - } - }, - session.toPlainObject()); - return session.getRawSession() - .then(function(rawSession) { - assertEquals(jwt, rawSession); - }); -} - - -function testMultiFactorSession_pendingCredential() { - var session = new fireauth.MultiFactorSession(null, pendingCredential); - - assertEquals( - session.type, fireauth.MultiFactorSession.Type.SIGN_IN); - assertObjectEquals( - { - 'multiFactorSession': { - 'pendingCredential': pendingCredential - } - }, - session.toPlainObject()); - return session.getRawSession() - .then(function(rawSession) { - assertEquals(pendingCredential, rawSession); - }); -} - - -function testMultiFactorSession_missingRawSession() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Internal assert: no raw session string available'); - - var error = assertThrows(function() { - return new fireauth.MultiFactorSession(null, null); - }); - - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); -} - - -function testMultiFactorSession_undeterminedSessionType() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Internal assert: unable to determine the session type'); - - var error = assertThrows(function() { - return new fireauth.MultiFactorSession(jwt, pendingCredential); - }); - - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); -} - - -function testMultiFactorSession_fromPlainObject_valid() { - var enrollSession = new fireauth.MultiFactorSession(jwt, null); - var signInSession = new fireauth.MultiFactorSession(null, pendingCredential); - var enrollSessionObject = { - 'multiFactorSession': { - 'idToken': jwt - } - }; - var signInSessionObject = { - 'multiFactorSession': { - 'pendingCredential': pendingCredential - } - }; - - assertObjectEquals( - enrollSession, - fireauth.MultiFactorSession.fromPlainObject(enrollSessionObject)); - assertObjectEquals( - signInSession, - fireauth.MultiFactorSession.fromPlainObject(signInSessionObject)); -} - - -function testMultiFactorSession_fromPlainObject_invalid() { - assertNull( - fireauth.MultiFactorSession.fromPlainObject(null)); - assertNull( - fireauth.MultiFactorSession.fromPlainObject({})); - assertNull( - fireauth.MultiFactorSession.fromPlainObject({ - 'idToken': jwt - })); - assertNull( - fireauth.MultiFactorSession.fromPlainObject({ - 'pendingCredential': pendingCredential - })); - assertNull( - fireauth.MultiFactorSession.fromPlainObject({ - 'multiFactorSession': {} - })); - assertNull( - fireauth.MultiFactorSession.fromPlainObject({ - 'multiFactorSession': { - 'idToken': '' - } - })); - assertNull( - fireauth.MultiFactorSession.fromPlainObject({ - 'multiFactorSession': { - 'pendingCredential': '' - } - })); -} diff --git a/packages/auth/test/multifactoruser_test.js b/packages/auth/test/multifactoruser_test.js deleted file mode 100644 index 142f316bf86..00000000000 --- a/packages/auth/test/multifactoruser_test.js +++ /dev/null @@ -1,1192 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for multifactoruser.js - */ - -goog.provide('fireauth.MultiFactorUserTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthUser'); -goog.require('fireauth.MultiFactorAssertion'); -goog.require('fireauth.MultiFactorInfo'); -goog.require('fireauth.MultiFactorSession'); -goog.require('fireauth.MultiFactorUser'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.UserEventType'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('goog.Promise'); -goog.require('goog.array'); -goog.require('goog.events'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.MultiFactorUserTest'); - - -var mockControl; -var now = new Date(); -var testUser; -var config; -var accountInfo; -var accountInfo2; -var accountInfoWithUid1; -var singleFactorAccountInfo; -var updatedTokenResponse; -var tokenResponse; -var expiredTokenResponse; -var singleFactorTokenResponse; -var getAccountInfoResponse1; -var getAccountInfoResponse2; -var getAccountInfoResponseWithUid1; -var getAccountInfoResponseWithUid2; -var getAccountInfoResponseWithNoSecondFactors; - - -function setUp() { - config = { - 'apiKey': 'API_KEY', - 'appName': 'appId1' - }; - accountInfo = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'multiFactor': { - 'enrolledFactors': [ - { - 'uid': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrollmentTime': now.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505551234' - }, - { - 'uid': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrollmentTime': now.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505556789' - } - ] - } - }; - accountInfo2 = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'multiFactor': { - 'enrolledFactors': [ - { - 'uid': 'ENROLLMENT_UID3', - 'displayName': 'Backup phone', - 'enrollmentTime': now.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505551111' - }, - { - 'uid': 'ENROLLMENT_UID4', - 'displayName': 'Personal phone number', - 'enrollmentTime': now.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505552222' - }, - ] - } - }; - accountInfoWithUid1 = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'multiFactor': { - 'enrolledFactors': [ - { - 'uid': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrollmentTime': now.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505551234' - } - ] - } - }; - // Single factor account info. - singleFactorAccountInfo = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true - }; - tokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }), - 'refreshToken': 'REFRESH_TOKEN1' - }; - expiredTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }, now - 1), - 'refreshToken': 'REFRESH_TOKEN1' - }; - singleFactorTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password' - } - }), - 'refreshToken': 'REFRESH_TOKEN1' - }; - updatedTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }) + 'UPDATED', // Modify the JWT string so the idToken is different. - 'refreshToken': 'REFRESH_TOKEN2' - }; - getAccountInfoResponse1 = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': 'user@default.com', - 'emailVerified': true, - 'displayName': 'defaultDisplayName', - 'providerUserInfo': [], - 'photoUrl': 'https://www.default.com/default/default.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'lastLoginAt': '1506050282000', - 'createdAt': '1506050282000', - 'mfaInfo': [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505556789' - } - ] - }] - }; - getAccountInfoResponse2 = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': 'user@default.com', - 'emailVerified': true, - 'displayName': 'defaultDisplayName', - 'providerUserInfo': [], - 'photoUrl': 'https://www.default.com/default/default.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'lastLoginAt': '1506050282000', - 'createdAt': '1506050282000', - 'mfaInfo': [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID3', - 'displayName': 'Backup phone', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551111' - }, - { - 'mfaEnrollmentId': 'ENROLLMENT_UID4', - 'displayName': 'Personal phone number', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505552222' - } - ] - }] - }; - getAccountInfoResponseWithUid1 = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': 'user@default.com', - 'emailVerified': true, - 'displayName': 'defaultDisplayName', - 'providerUserInfo': [], - 'photoUrl': 'https://www.default.com/default/default.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'lastLoginAt': '1506050282000', - 'createdAt': '1506050282000', - 'mfaInfo': [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - } - ] - }] - }; - getAccountInfoResponseWithUid2 = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': 'user@default.com', - 'emailVerified': true, - 'displayName': 'defaultDisplayName', - 'providerUserInfo': [], - 'photoUrl': 'https://www.default.com/default/default.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'lastLoginAt': '1506050282000', - 'createdAt': '1506050282000', - 'mfaInfo': [ - { - 'mfaEnrollmentId': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505556789' - } - ] - }] - }; - getAccountInfoResponseWithNoSecondFactors = { - 'users': [{ - 'localId': 'defaultUserId', - 'email': 'user@default.com', - 'emailVerified': true, - 'displayName': 'defaultDisplayName', - 'providerUserInfo': [], - 'photoUrl': 'https://www.default.com/default/default.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false, - 'lastLoginAt': '1506050282000', - 'createdAt': '1506050282000', - 'mfaInfo': [{}] - }] - }; - testUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - mockControl = new goog.testing.MockControl(); -} - - -function tearDown() { - testUser = null; - config = null; - accountInfo = null; - accountInfo2 = null; - accountInfoWithUid1 = null; - singleFactorAccountInfo = null; - tokenResponse = null; - expiredTokenResponse = null; - singleFactorTokenResponse = null; - getAccountInfoResponse1 = null; - getAccountInfoResponse2 = null; - getAccountInfoResponseWithUid1 = null; - getAccountInfoResponseWithNoSecondFactors = null; - mockControl.$verifyAll(); - mockControl.$tearDown(); -} - - -function testMultiFactorUser() { - var info = [ - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][0]), - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][1]) - ]; - testUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - var multiFactorUser = new fireauth.MultiFactorUser(testUser, accountInfo); - - assertEquals(2, multiFactorUser['enrolledFactors'].length); - assertObjectEquals(info[0], multiFactorUser['enrolledFactors'][0]); - assertObjectEquals(info[1], multiFactorUser['enrolledFactors'][1]); - assertEquals(testUser, multiFactorUser.getUser()); -} - - -function testMultiFactorUser_copy() { - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(tokenResponse.idToken).$returns( - goog.Promise.resolve(getAccountInfoResponse1)).$once(); - getAccountInfoByIdToken(tokenResponse.idToken).$returns( - goog.Promise.resolve(getAccountInfoResponse2)).$once(); - mockControl.$replayAll(); - var info = [ - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][0]), - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][1]), - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo2['multiFactor']['enrolledFactors'][0]), - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo2['multiFactor']['enrolledFactors'][1]), - ]; - testUser = new fireauth.AuthUser(config, tokenResponse); - var testUser2 = new fireauth.AuthUser(config, tokenResponse, accountInfo); - var multiFactorUser1 = new fireauth.MultiFactorUser(testUser, accountInfo); - var multiFactorUser2 = new fireauth.MultiFactorUser(testUser2, accountInfo2); - - // Confirm multiFactorUser1 and multiFactorUser2 initialized with correct - // factors. - assertEquals(2, multiFactorUser1['enrolledFactors'].length); - assertObjectEquals(info[0], multiFactorUser1['enrolledFactors'][0]); - assertObjectEquals(info[1], multiFactorUser1['enrolledFactors'][1]); - assertEquals(2, multiFactorUser2['enrolledFactors'].length); - assertObjectEquals(info[2], multiFactorUser2['enrolledFactors'][0]); - assertObjectEquals(info[3], multiFactorUser2['enrolledFactors'][1]); - - // Copy multiFactorUser2 to multiFactorUser1. - multiFactorUser1.copy(multiFactorUser2); - assertEquals(2, multiFactorUser1.enrolledFactors.length); - // Enrolled factors should be updated on multiFactorUser1 (copied from - // multiFactorUser2). - assertObjectEquals(info[2], multiFactorUser1['enrolledFactors'][0]); - assertObjectEquals(info[3], multiFactorUser1['enrolledFactors'][1]); - assertEquals(testUser, multiFactorUser1.getUser()); - assertEquals(testUser2, multiFactorUser2.getUser()); - - // First user reload should still trigger changes to enrolledFactors as the - // same user is still linked to multiFactorUser1. - return testUser.reload() - .then(function() { - // Enrolled factors should be updated on multiFactorUser1. - assertEquals(2, multiFactorUser1['enrolledFactors'].length); - assertObjectEquals(info[0], multiFactorUser1['enrolledFactors'][0]); - assertObjectEquals(info[1], multiFactorUser1['enrolledFactors'][1]); - // Reload second user. This should not update the enrolled factors on - // multiFactorUser1. - return testUser2.reload(); - }) - .then(function() { - // multiFactorUser1 is unchanged. - assertEquals(2, multiFactorUser1['enrolledFactors'].length); - assertObjectEquals(info[0], multiFactorUser1['enrolledFactors'][0]); - assertObjectEquals(info[1], multiFactorUser1['enrolledFactors'][1]); - }); -} - - -function testMultiFactorUser_userReload() { - var info = [ - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][0]), - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][1]), - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo2['multiFactor']['enrolledFactors'][0]), - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo2['multiFactor']['enrolledFactors'][1]), - ]; - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(tokenResponse.idToken).$returns( - goog.Promise.resolve(getAccountInfoResponse1)).$once(); - getAccountInfoByIdToken(tokenResponse.idToken).$returns( - goog.Promise.resolve(getAccountInfoResponse2)).$once(); - mockControl.$replayAll(); - - testUser = new fireauth.AuthUser(config, tokenResponse); - var multiFactorUser = new fireauth.MultiFactorUser(testUser); - - assertEquals(0, multiFactorUser['enrolledFactors'].length); - // Trigger user reloaded. - return testUser.reload() - .then(function() { - // Enrolled factors should be updated with first GetAccountInfo result. - assertEquals(2, multiFactorUser['enrolledFactors'].length); - assertObjectEquals(info[0], multiFactorUser['enrolledFactors'][0]); - assertObjectEquals(info[1], multiFactorUser['enrolledFactors'][1]); - // Reload again. - return testUser.reload(); - }).then(function() { - // Enrolled factors should be updated with second GetAccountInfo result. - assertEquals(2, multiFactorUser['enrolledFactors'].length); - assertObjectEquals(info[2], multiFactorUser['enrolledFactors'][0]); - assertObjectEquals(info[3], multiFactorUser['enrolledFactors'][1]); - }); -} - - -function testMultiFactorUser_toPlainObject() { - testUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - var multiFactorUser = new fireauth.MultiFactorUser(testUser, accountInfo); - var emptyMultiFactorUser = new fireauth.MultiFactorUser(testUser); - - assertObjectEquals( - { - 'multiFactor': { - 'enrolledFactors': goog.array.clone( - accountInfo['multiFactor']['enrolledFactors']) - } - }, - multiFactorUser.toPlainObject()); - - assertObjectEquals( - { - 'multiFactor': {'enrolledFactors': []} - }, - emptyMultiFactorUser.toPlainObject()); -} - - -function testMultiFactorUser_getSession_cached() { - var requestStsToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'requestStsToken'); - requestStsToken().$times(0); - mockControl.$replayAll(); - - var expectedSession = new fireauth.MultiFactorSession( - tokenResponse.idToken, null); - var multiFactorUser = new fireauth.MultiFactorUser(testUser); - - return multiFactorUser.getSession() - .then(function(actualSession) { - assertObjectEquals(expectedSession, actualSession); - }); -} - - -function testMultiFactorUser_getSession_expired() { - var tokenChanges = 0; - var newJwt = fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }); - var requestStsToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'requestStsToken'); - requestStsToken({ - 'grant_type': 'refresh_token', - 'refresh_token': 'REFRESH_TOKEN1' - }).$returns(goog.Promise.resolve({ - 'access_token': newJwt, - 'refresh_token': 'REFRESH_TOKEN2', - 'expires_in': '3600' - })).$once(); - mockControl.$replayAll(); - - testUser = new fireauth.AuthUser(config, expiredTokenResponse, accountInfo); - var expectedSession = new fireauth.MultiFactorSession(newJwt, null); - var multiFactorUser = new fireauth.MultiFactorUser(testUser); - - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanges++; - }); - return multiFactorUser.getSession() - .then(function(actualSession) { - assertEquals(1, tokenChanges); - assertObjectEquals(expectedSession, actualSession); - // Refresh token updated on user. - assertEquals('REFRESH_TOKEN2', testUser['refreshToken']); - return testUser.getIdToken(); - }) - .then(function(idToken) { - // ID token updated on user. - assertEquals(newJwt, idToken); - }); -} - - -function testMultiFactorUser_getSession_error() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - var requestStsToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'requestStsToken'); - requestStsToken({ - 'grant_type': 'refresh_token', - 'refresh_token': 'REFRESH_TOKEN1' - }).$returns(goog.Promise.reject(expectedError)).$once(); - mockControl.$replayAll(); - - testUser = new fireauth.AuthUser(config, expiredTokenResponse, accountInfo); - var multiFactorUser = new fireauth.MultiFactorUser(testUser); - - return multiFactorUser.getSession() - .then(fail) - .thenCatch(function(error) { - assertEquals(expectedError, error); - }); -} - - -function testMultiFactorUser_enroll_singleFactorUser() { - // Tests a single factor user enrolling a second factor without providing a - // second factor display name. - var expectedSession = new fireauth.MultiFactorSession( - singleFactorTokenResponse.idToken); - // Create a single-factor user. - testUser = new fireauth.AuthUser( - config, singleFactorTokenResponse, singleFactorAccountInfo); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - // Multi-factor info without display name. - var expectedInfo = new fireauth.MultiFactorInfo.fromPlainObject({ - 'uid': 'ENROLLMENT_UID1', - 'enrollmentTime': now.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505551234' - }); - var tokenChanged = 0; - var stateChanged = 0; - - // Simulate assertion processing resolves with the successful token response. - mockAssertion.process(testUser.getRpcHandler(), expectedSession, undefined) - .$once() - // New MFA token will be issued after enrollment. - .$returns(goog.Promise.resolve(tokenResponse)); - getAccountInfoByIdToken(tokenResponse.idToken) - .$returns(goog.Promise.resolve(getAccountInfoResponseWithUid1)).$once(); - mockControl.$replayAll(); - - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, singleFactorAccountInfo); - assertEquals(0, multiFactorUser['enrolledFactors'].length); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - - // Enroll the second factor without providing a display name. - return multiFactorUser.enroll(mockAssertion).then(function() { - // The new second factor info should be added. - assertEquals(1, multiFactorUser['enrolledFactors'].length); - assertObjectEquals(expectedInfo, multiFactorUser['enrolledFactors'][0]); - // Token changed listeners should be triggered. - assertEquals(1, tokenChanged); - assertEquals(1, stateChanged); - }); -} - - -function testMultiFactorUser_enroll_multiFactorUser() { - // Tests a multi-factor user enrolling a new second factor with a display - // name. - var expectedSession = new fireauth.MultiFactorSession( - tokenResponse.idToken); - testUser = new fireauth.AuthUser( - config, tokenResponse, accountInfoWithUid1); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - var info = [ - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][0]), - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][1]) - ]; - var newTokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt({ - 'firebase': { - 'sign_in_provider': 'password', - 'sign_in_second_factor': 'phone' - } - }), - 'refreshToken': 'REFRESH_TOKEN1' - }; - var tokenChanged = 0; - var stateChanged = 0; - - // Simulate assertion processing resolves with the successful token response. - mockAssertion.process( - testUser.getRpcHandler(), expectedSession, 'Spouse phone number') - .$once() - // New token will be issued after enrollment. - .$returns(goog.Promise.resolve(newTokenResponse)); - getAccountInfoByIdToken(newTokenResponse.idToken) - .$returns(goog.Promise.resolve(getAccountInfoResponse1)).$once(); - mockControl.$replayAll(); - - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, accountInfoWithUid1); - assertEquals(1, multiFactorUser['enrolledFactors'].length); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - - // Enroll a new second factor with a display name. - return multiFactorUser.enroll(mockAssertion, 'Spouse phone number') - .then(function() { - // The new second factor info should be added. - assertEquals(2, multiFactorUser['enrolledFactors'].length); - assertObjectEquals(info[0], multiFactorUser['enrolledFactors'][0]); - assertObjectEquals(info[1], multiFactorUser['enrolledFactors'][1]); - // Token changed listeners should be triggered. - assertEquals(1, tokenChanged); - assertEquals(1, stateChanged); - }); -} - - -function testMultiFactorUser_enroll_tokenExpired() { - // Tests that the token is expired when trying to enroll a second factor. - var tokenChanged = 0; - var stateChanged = 0; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - var requestStsToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'requestStsToken'); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - - requestStsToken({ - 'grant_type': 'refresh_token', - 'refresh_token': 'REFRESH_TOKEN1' - }).$returns(goog.Promise.reject(expectedError)).$once(); - mockAssertion.process( - goog.testing.mockmatchers.ignoreArgument, - goog.testing.mockmatchers.ignoreArgument, - goog.testing.mockmatchers.ignoreArgument) - .$times(0); - mockControl.$replayAll(); - - // Create a single-factor user. - testUser = new fireauth.AuthUser( - config, expiredTokenResponse, singleFactorAccountInfo); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, singleFactorAccountInfo); - assertEquals(0, multiFactorUser['enrolledFactors'].length); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - - return multiFactorUser.enroll(mockAssertion) - .then(fail) - .thenCatch(function(error) { - assertEquals(0, multiFactorUser['enrolledFactors'].length); - // TOKEN_EXPIRED error should be thrown and token changed listeners - // should not be triggered. - assertEquals(expectedError, error); - assertEquals(0, tokenChanged); - assertEquals(0, stateChanged); - }); -} - - -function testMultiFactorUser_enroll_codeExpiredError() { - // Tests that error is thrown when enrolling the second factor. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CODE_EXPIRED); - var expectedSession = new fireauth.MultiFactorSession( - singleFactorTokenResponse.idToken); - // Create a single-factor user. - testUser = new fireauth.AuthUser( - config, singleFactorTokenResponse, singleFactorAccountInfo); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - var tokenChanged = 0; - var stateChanged = 0; - - // Simulate assertion processing rejects with error. - mockAssertion.process( - testUser.getRpcHandler(), expectedSession, undefined) - .$once() - .$returns(goog.Promise.reject(expectedError)); - // GetAccountInfo should not be called. - getAccountInfoByIdToken(goog.testing.mockmatchers.ignoreArgument).$times(0); - mockControl.$replayAll(); - - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, singleFactorAccountInfo); - assertEquals(0, multiFactorUser['enrolledFactors'].length); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - - return multiFactorUser.enroll(mockAssertion) - .then(fail) - .thenCatch(function(error) { - assertEquals(0, multiFactorUser['enrolledFactors'].length); - assertEquals(expectedError, error); - assertEquals(0, tokenChanged); - assertEquals(0, stateChanged); - }); -} - - -function testMultiFactorUser_enroll_userDeleted() { - // Tests that enrollment should fail if the user is deleted. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.MODULE_DESTROYED); - var mockAssertion = mockControl.createStrictMock( - fireauth.MultiFactorAssertion); - var tokenChanged = 0; - var stateChanged = 0; - - mockAssertion.process( - goog.testing.mockmatchers.ignoreArgument, - goog.testing.mockmatchers.ignoreArgument, - goog.testing.mockmatchers.ignoreArgument) - .$times(0); - mockControl.$replayAll(); - - testUser = new fireauth.AuthUser( - config, singleFactorTokenResponse, singleFactorAccountInfo); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, singleFactorAccountInfo); - assertEquals(0, multiFactorUser['enrolledFactors'].length); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - assertEquals(0, multiFactorUser['enrolledFactors'].length); - - testUser.destroy(); - return multiFactorUser.enroll(mockAssertion) - .then(fail) - .thenCatch(function(error) { - assertEquals(0, multiFactorUser['enrolledFactors'].length); - assertEquals(expectedError.code, error.code); - assertEquals(0, tokenChanged); - assertEquals(0, stateChanged); - }); -} - - -function testMultiFactorUser_unenroll_success() { - // Tests that a second factor can be successfully unenrolled. - var tokenChanged = 0; - var stateChanged = 0; - var userInvalidated = 0; - - // Set up mocks. - var withdrawMfa = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'withdrawMfa'); - // First, expect withdrawMfa to be called with the original tokens, then - // it will return the updated pair. - withdrawMfa(tokenResponse.idToken, 'ENROLLMENT_UID1') - .$returns({ - 'idToken': updatedTokenResponse.idToken, - 'refreshToken': updatedTokenResponse.refreshToken - }).$once(); - // Next, expect getAccountInfoByToken to be called with the updated idToken. - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(updatedTokenResponse.idToken) - .$returns(goog.Promise.resolve(getAccountInfoResponseWithUid2)).$once(); - mockControl.$replayAll(); - - // Create the user with the intital pair of tokens and listen for events. - testUser = new fireauth.AuthUser( - config, tokenResponse, accountInfo); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, accountInfo); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - goog.events.listen( - testUser, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidated++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - assertEquals(2, multiFactorUser['enrolledFactors'].length); - - // Run test. - return multiFactorUser.unenroll('ENROLLMENT_UID1') - .then(function(){ - // The tokens should have been updated. - var tokens = testUser.getStsTokenManager().toPlainObject(); - assertEquals(updatedTokenResponse.idToken, tokens['accessToken']); - assertEquals(updatedTokenResponse.refreshToken, tokens['refreshToken']); - assertEquals(1, tokenChanged); - // The first enrolled factor should have been removed. - assertEquals(1, multiFactorUser['enrolledFactors'].length); - assertObjectEquals( - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][1]), - multiFactorUser['enrolledFactors'][0]); - assertEquals(1, stateChanged); - // The user should not be invalidated. - assertEquals(0, userInvalidated); - }); -} - - -function testMultiFactorUser_unenroll_successWithMultiFactorInfo() { - // Tests that a second factor can be successfully unenrolled when passed - // as a MultiFactorInfo object instead of as a string. - var tokenChanged = 0; - var stateChanged = 0; - var userInvalidated = 0; - - // Set up mocks. - var withdrawMfa = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'withdrawMfa'); - // First, expect withdrawMfa to be called with the original tokens, then - // it will return the updated pair. - withdrawMfa(tokenResponse.idToken, 'ENROLLMENT_UID1') - .$returns({ - 'idToken': updatedTokenResponse.idToken, - 'refreshToken': updatedTokenResponse.refreshToken - }).$once(); - // Next, expect getAccountInfoByToken to be called with the updated idToken. - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(updatedTokenResponse.idToken) - .$returns(goog.Promise.resolve(getAccountInfoResponseWithUid2)).$once(); - mockControl.$replayAll(); - - // Create the user with the intital pair of tokens and listen for events. - testUser = new fireauth.AuthUser( - config, tokenResponse, accountInfo); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, accountInfo); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - goog.events.listen( - testUser, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidated++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - assertEquals(2, multiFactorUser['enrolledFactors'].length); - - // Run test. - var multiFactorInfo = fireauth.MultiFactorInfo.fromPlainObject({ - 'uid': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrollmentTime': now.toUTCString(), - 'phoneNumber': '+16505551234' - }); - return multiFactorUser.unenroll(multiFactorInfo) - .then(function(){ - // The tokens should have been updated. - var tokens = testUser.getStsTokenManager().toPlainObject(); - assertEquals(updatedTokenResponse.idToken, tokens['accessToken']); - assertEquals(updatedTokenResponse.refreshToken, tokens['refreshToken']); - assertEquals(1, tokenChanged); - // The first enrolled factor should have been removed. - assertEquals(1, multiFactorUser['enrolledFactors'].length); - assertObjectEquals( - fireauth.MultiFactorInfo.fromPlainObject( - accountInfo['multiFactor']['enrolledFactors'][1]), - multiFactorUser['enrolledFactors'][0]); - assertEquals(1, stateChanged); - // The user should not be invalidated. - assertEquals(0, userInvalidated); - }); -} - - -function testMultiFactorUser_unenroll_successRemovingAllSecondFactors() { - // Tests that the user can be downgraded to a single factor (all second - // factors on a user can be removed). - var tokenChanged = 0; - var stateChanged = 0; - var userInvalidated = 0; - - // Set up mocks. - var withdrawMfa = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'withdrawMfa'); - // First, expect withdrawMfa to be called with the original tokens, then - // it will return a single factor token response. - withdrawMfa(tokenResponse.idToken, 'ENROLLMENT_UID1') - .$returns({ - 'idToken': singleFactorTokenResponse.idToken, - 'refreshToken': singleFactorTokenResponse.refreshToken - }).$once(); - // Next, expect getAccountInfoByToken to be called with the updated idToken. - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(singleFactorTokenResponse.idToken) - .$returns(goog.Promise.resolve(getAccountInfoResponseWithNoSecondFactors)) - .$once(); - mockControl.$replayAll(); - - // Create the user with the intital pair of tokens and listen for events. - testUser = new fireauth.AuthUser( - config, tokenResponse, accountInfoWithUid1); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, accountInfoWithUid1); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - goog.events.listen( - testUser, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidated++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - assertEquals(1, multiFactorUser['enrolledFactors'].length); - - // Run test. - return multiFactorUser.unenroll('ENROLLMENT_UID1') - .then(function(){ - // The tokens should have been updated. - var tokens = testUser.getStsTokenManager().toPlainObject(); - assertEquals( - singleFactorTokenResponse.idToken, - tokens['accessToken']); - assertEquals( - singleFactorTokenResponse.refreshToken, - tokens['refreshToken']); - assertEquals(1, tokenChanged); - // All enrolled factors should be removed. - assertEquals(0, multiFactorUser['enrolledFactors'].length); - assertEquals(1, stateChanged); - // The user should not be invalidated. - assertEquals(0, userInvalidated); - }); -} - - -function testMultiFactorUser_unenroll_successWithEmptyTokenResponse() { - // Tests the situation where the backend decides to revoke the user's session - // because they unenrolled the factor that they used to login with. In this - // case an empty object is returned instead of new tokens. The unenroll call - // should succeed, but the user should be invalidated. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - var tokenChanged = 0; - var stateChanged = 0; - var userInvalidated = 0; - - // Set up mocks. - var withdrawMfa = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'withdrawMfa'); - withdrawMfa(tokenResponse.idToken, 'ENROLLMENT_UID1') - .$returns({}).$once(); // The empty token response. - var getAccountInfoByIdToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken'); - getAccountInfoByIdToken(tokenResponse.idToken) - .$returns(goog.Promise.reject(expectedError)).$once(); - mockControl.$replayAll(); - - // Create user. - testUser = new fireauth.AuthUser( - config, tokenResponse, accountInfo); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, accountInfo); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - goog.events.listen( - testUser, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidated++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - assertEquals(2, multiFactorUser['enrolledFactors'].length); - - // Run test. - return multiFactorUser.unenroll('ENROLLMENT_UID1') - .then(function() { - assertEquals(0, tokenChanged); - assertEquals(0, stateChanged); - // The first enrolled factor should have been removed. - assertEquals(1, multiFactorUser['enrolledFactors'].length); - // The user should be invalidated. - assertEquals(1, userInvalidated); - }); -} - - -function testMultiFactorUser_unenroll_tokenExpired() { - // Tests that unenroll will fail when the user's token is expired. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.TOKEN_EXPIRED); - var tokenChanged = 0; - var stateChanged = 0; - var userInvalidated = 0; - - // Set up mocks. - var requestStsToken = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'requestStsToken'); - requestStsToken({ - 'grant_type': 'refresh_token', - 'refresh_token': 'REFRESH_TOKEN1' - }).$returns(goog.Promise.reject(expectedError)).$once(); - mockControl.$replayAll(); - - // Create user. - testUser = new fireauth.AuthUser( - config, expiredTokenResponse, accountInfoWithUid1); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, accountInfo); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - goog.events.listen( - testUser, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidated++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - assertEquals(2, multiFactorUser['enrolledFactors'].length); - - // Run test. - return multiFactorUser.unenroll('ENROLLMENT_UID1') - .then(fail) - .thenCatch(function(error){ - assertEquals(expectedError, error); - // The enrolled factors should be unchanged. - assertEquals(2, multiFactorUser['enrolledFactors'].length); - assertEquals(0, tokenChanged); - assertEquals(0, stateChanged); - // The user should be invalidated. - assertEquals(1, userInvalidated); - }); -} - - -function testMultiFactorUser_unenroll_requiresRecentLogin() { - // Tests that unenroll will fail when the user's credentials are too old and - // they need to reauthenticate. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.CREDENTIAL_TOO_OLD_LOGIN_AGAIN); - var tokenChanged = 0; - var stateChanged = 0; - var userInvalidated = 0; - - // Set up mocks. - var withdrawMfa = mockControl.createMethodMock( - fireauth.RpcHandler.prototype, 'withdrawMfa'); - withdrawMfa(tokenResponse.idToken, 'ENROLLMENT_UID1') - .$returns(goog.Promise.reject(expectedError)).$once(); - mockControl.$replayAll(); - - // Create user. - testUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, accountInfo); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - goog.events.listen( - testUser, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidated++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - assertEquals(2, multiFactorUser['enrolledFactors'].length); - - // Run test. - return multiFactorUser.unenroll('ENROLLMENT_UID1') - .then(fail) - .thenCatch(function(error){ - assertEquals(expectedError, error); - // Tokens should be unchanged. - var tokens = testUser.getStsTokenManager().toPlainObject(); - assertEquals(tokenResponse.idToken, tokens['accessToken']); - assertEquals(tokenResponse.refreshToken, tokens['refreshToken']); - // The enrolled factors should be unchanged. - assertEquals(2, multiFactorUser['enrolledFactors'].length); - assertEquals(0, tokenChanged); - assertEquals(0, stateChanged); - // The user should not be invalidated (since TOKEN_EXPIRED was not - // thrown). - assertEquals(0, userInvalidated); - }); -} - - -function testMultiFactorUser_unenroll_userDeleted() { - // Tests that unenroll will fail when the user's account has been deleted. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.MODULE_DESTROYED); - var tokenChanged = 0; - var stateChanged = 0; - var userInvalidated = 0; - - // Create user. - testUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - var multiFactorUser = new fireauth.MultiFactorUser( - testUser, accountInfo); - goog.events.listen( - testUser, fireauth.UserEventType.TOKEN_CHANGED, function(event) { - tokenChanged++; - }); - goog.events.listen( - testUser, fireauth.UserEventType.USER_INVALIDATED, function(event) { - userInvalidated++; - }); - testUser.addStateChangeListener(function(user) { - stateChanged++; - }); - assertEquals(2, multiFactorUser['enrolledFactors'].length); - - // Run test. - testUser.destroy(); - return multiFactorUser.unenroll('ENROLLMENT_UID1') - .then(fail) - .thenCatch(function(error){ - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Tokens should be unchanged. - var tokens = testUser.getStsTokenManager().toPlainObject(); - assertEquals(tokenResponse.idToken, tokens['accessToken']); - assertEquals(tokenResponse.refreshToken, tokens['refreshToken']); - // The enrolled factors should be unchanged. - assertEquals(2, multiFactorUser['enrolledFactors'].length); - assertEquals(0, tokenChanged); - assertEquals(0, stateChanged); - // The user should not be invalidated (since TOKEN_EXPIRED was not - // thrown). - assertEquals(0, userInvalidated); - }); -} diff --git a/packages/auth/test/oauthhelperstate_test.js b/packages/auth/test/oauthhelperstate_test.js deleted file mode 100644 index 8a42d98660d..00000000000 --- a/packages/auth/test/oauthhelperstate_test.js +++ /dev/null @@ -1,383 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for oauthhelperstate.js - */ - -goog.provide('fireauth.OAuthHelperStateTest'); - -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.OAuthHelperState'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.OAuthHelperStateTest'); - - -var state; -var state2; -var state3; -var state4; -var state5; -var state6; -var state7; -var stateObject; -var stateObject2; -var stateObject3; -var stateObject4; -var stateObject5; -var stateObject6; -var stateObject7; - - -function setUp() { - state = new fireauth.OAuthHelperState( - 'API_KEY', - 'APP_NAME', - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - null, - 'http://www.example.com/redirect', - '3.0.0'); - state2 = new fireauth.OAuthHelperState( - 'API_KEY', - 'APP_NAME', - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - '12345678'); - state3 = new fireauth.OAuthHelperState( - 'API_KEY', - 'APP_NAME', - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - '12345678', - null, - '3.6.0', - 'Test App', - 'com.example.android', - null, - 't', - ['firebaseui', 'angularfire']); - state4 = new fireauth.OAuthHelperState( - 'API_KEY', - 'APP_NAME', - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - '12345678', - null, - '3.6.0', - 'Test App', - null, - 'com.example.ios', - 's'); - // State with OAuth client ID. - state5 = new fireauth.OAuthHelperState( - 'API_KEY', - 'APP_NAME', - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - '12345678', - null, - '3.6.0', - 'Test App', - null, - 'com.example.ios', - null, - null, - '123456.apps.googleusercontent.com'); - // State with SHA-1 cert. - state6 = new fireauth.OAuthHelperState( - 'API_KEY', - 'APP_NAME', - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - '12345678', - null, - '3.6.0', - 'Test App', - 'com.example.android', - null, - null, - null, - null, - 'SHA_1_ANDROID_CERT'); - // State with tenant ID. - state7 = new fireauth.OAuthHelperState( - 'API_KEY', - 'APP_NAME', - fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - null, - 'http://www.example.com/redirect', - '3.0.0', - null, - null, - null, - null, - null, - null, - null, - 'TENANT_ID'); - stateObject = { - 'apiKey': 'API_KEY', - 'appName': 'APP_NAME', - 'type': fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - 'redirectUrl': 'http://www.example.com/redirect', - 'eventId': null, - 'clientVersion': '3.0.0', - 'displayName': null, - 'apn': null, - 'ibi': null, - 'eid': null, - 'fw': [], - 'clientId': null, - 'sha1Cert': null, - 'tenantId': null - }; - stateObject2 = { - 'apiKey': 'API_KEY', - 'appName': 'APP_NAME', - 'type': fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - 'redirectUrl': null, - 'eventId': '12345678', - 'clientVersion': null, - 'displayName': null, - 'apn': null, - 'ibi': null, - 'eid': null, - 'fw': [], - 'clientId': null, - 'sha1Cert': null, - 'tenantId': null - }; - stateObject3 = { - 'apiKey': 'API_KEY', - 'appName': 'APP_NAME', - 'type': fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - 'redirectUrl': null, - 'eventId': '12345678', - 'clientVersion': '3.6.0', - 'displayName': 'Test App', - 'apn': 'com.example.android', - 'ibi': null, - 'eid': 't', - 'fw': ['firebaseui', 'angularfire'], - 'clientId': null, - 'sha1Cert': null, - 'tenantId': null - }; - stateObject4 = { - 'apiKey': 'API_KEY', - 'appName': 'APP_NAME', - 'type': fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - 'redirectUrl': null, - 'eventId': '12345678', - 'clientVersion': '3.6.0', - 'displayName': 'Test App', - 'apn': null, - 'ibi': 'com.example.ios', - 'eid': 's', - 'fw': [], - 'clientId': null, - 'sha1Cert': null, - 'tenantId': null - }; - stateObject5 = { - 'apiKey': 'API_KEY', - 'appName': 'APP_NAME', - 'type': fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - 'redirectUrl': null, - 'eventId': '12345678', - 'clientVersion': '3.6.0', - 'displayName': 'Test App', - 'apn': null, - 'ibi': 'com.example.ios', - 'eid': null, - 'fw': [], - 'clientId': '123456.apps.googleusercontent.com', - 'sha1Cert': null, - 'tenantId': null - }; - stateObject6 = { - 'apiKey': 'API_KEY', - 'appName': 'APP_NAME', - 'type': fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - 'redirectUrl': null, - 'eventId': '12345678', - 'clientVersion': '3.6.0', - 'displayName': 'Test App', - 'apn': 'com.example.android', - 'ibi': null, - 'eid': null, - 'fw': [], - 'clientId': null, - 'sha1Cert': 'SHA_1_ANDROID_CERT', - 'tenantId': null - }; - stateObject7 = { - 'apiKey': 'API_KEY', - 'appName': 'APP_NAME', - 'type': fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, - 'redirectUrl': 'http://www.example.com/redirect', - 'eventId': null, - 'clientVersion': '3.0.0', - 'displayName': null, - 'apn': null, - 'ibi': null, - 'eid': null, - 'fw': [], - 'clientId': null, - 'sha1Cert': null, - 'tenantId': 'TENANT_ID' - }; -} - - -function tearDown() { - state = null; - state2 = null; - state3 = null; - state4 = null; - state5 = null; - state6 = null; - state7 = null; - stateObject = null; - stateObject2 = null; - stateObject3 = null; - stateObject4 = null; - stateObject5 = null; - stateObject6 = null; - stateObject7 = null; -} - - -function testOAuthHelperState() { - // Check state. - assertEquals('API_KEY', state.getApiKey()); - assertEquals('APP_NAME', state.getAppName()); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, state.getType()); - assertEquals('http://www.example.com/redirect', state.getRedirectUrl()); - assertEquals('3.0.0', state.getClientVersion()); - assertNull(state.getEventId()); - assertNull(state.getDisplayName()); - assertNull(state.getApn()); - assertNull(state.getIbi()); - assertNull(state.getEndpointId()); - assertArrayEquals([], state.getFrameworks()); - assertNull(state.getClientId()); - assertNull(state.getSha1Cert()); - // Check state2. - assertEquals('12345678', state2.getEventId()); - assertNull(state2.getClientVersion()); - assertNull(state2.getRedirectUrl()); - assertNull(state2.getDisplayName()); - assertNull(state2.getApn()); - assertNull(state2.getIbi()); - assertNull(state2.getEndpointId()); - assertArrayEquals([], state2.getFrameworks()); - assertNull(state2.getClientId()); - assertNull(state2.getSha1Cert()); - // Check state3. - assertEquals(state3.getDisplayName(), 'Test App'); - assertEquals(state3.getApn(), 'com.example.android'); - assertNull(state3.getIbi()); - assertEquals('t', state3.getEndpointId()); - assertArrayEquals(['firebaseui', 'angularfire'], state3.getFrameworks()); - assertNull(state3.getClientId()); - assertNull(state3.getSha1Cert()); - // Check state4. - assertEquals(state4.getDisplayName(), 'Test App'); - assertNull(state4.getApn()); - assertEquals(state4.getIbi(), 'com.example.ios'); - assertEquals('s', state4.getEndpointId()); - assertArrayEquals([], state4.getFrameworks()); - assertNull(state4.getClientId()); - assertNull(state4.getSha1Cert()); - // Check state5. - assertNull(state4.getApn()); - assertEquals(state4.getIbi(), 'com.example.ios'); - assertNull(state2.getEndpointId()); - assertArrayEquals([], state4.getFrameworks()); - assertEquals('123456.apps.googleusercontent.com', state5.getClientId()); - assertNull(state5.getSha1Cert()); - // Check state6. - assertEquals(state3.getApn(), 'com.example.android'); - assertNull(state3.getIbi()); - assertNull(state2.getEndpointId()); - assertArrayEquals([], state4.getFrameworks()); - assertNull(state6.getClientId()); - assertEquals('SHA_1_ANDROID_CERT', state6.getSha1Cert()); - // Check state7. - assertEquals('API_KEY', state7.getApiKey()); - assertEquals('APP_NAME', state7.getAppName()); - assertEquals(fireauth.AuthEvent.Type.SIGN_IN_VIA_REDIRECT, state7.getType()); - assertEquals('http://www.example.com/redirect', state7.getRedirectUrl()); - assertEquals('3.0.0', state7.getClientVersion()); - assertNull(state7.getEventId()); - assertNull(state7.getDisplayName()); - assertNull(state7.getApn()); - assertNull(state7.getIbi()); - assertNull(state7.getEndpointId()); - assertArrayEquals([], state7.getFrameworks()); - assertNull(state7.getClientId()); - assertNull(state7.getSha1Cert()); - assertEquals('TENANT_ID', state7.getTenantId()); -} - - -function testOAuthHelperState_toPlainObject() { - assertObjectEquals( - stateObject, - state.toPlainObject()); - assertObjectEquals( - stateObject2, - state2.toPlainObject()); - assertObjectEquals( - stateObject3, - state3.toPlainObject()); - assertObjectEquals( - stateObject4, - state4.toPlainObject()); - assertObjectEquals( - stateObject5, - state5.toPlainObject()); - assertObjectEquals( - stateObject6, - state6.toPlainObject()); - assertObjectEquals( - stateObject7, - state7.toPlainObject()); -} - - -function testOAuthHelperState_fromPlainObject() { - assertObjectEquals( - state, - fireauth.OAuthHelperState.fromPlainObject(stateObject)); - assertObjectEquals( - state2, - fireauth.OAuthHelperState.fromPlainObject(stateObject2)); - assertObjectEquals( - state3, - fireauth.OAuthHelperState.fromPlainObject(stateObject3)); - assertObjectEquals( - state4, - fireauth.OAuthHelperState.fromPlainObject(stateObject4)); - assertObjectEquals( - state5, - fireauth.OAuthHelperState.fromPlainObject(stateObject5)); - assertObjectEquals( - state6, - fireauth.OAuthHelperState.fromPlainObject(stateObject6)); - assertObjectEquals( - state7, - fireauth.OAuthHelperState.fromPlainObject(stateObject7)); - assertNull(fireauth.OAuthHelperState.fromPlainObject({})); -} diff --git a/packages/auth/test/object_test.js b/packages/auth/test/object_test.js deleted file mode 100644 index 44f61223aac..00000000000 --- a/packages/auth/test/object_test.js +++ /dev/null @@ -1,318 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for object.js. - */ - -goog.provide('fireauth.objectTest'); - -goog.require('fireauth.deprecation'); -goog.require('fireauth.object'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); -goog.require('goog.userAgent'); - -goog.setTestOnly('fireauth.objectTest'); - -var stubs; - -function setUp() { - stubs = new goog.testing.PropertyReplacer(); -} - -function tearDown() { - stubs.reset(); -} - - -/** @type {boolean} True if the browser supports Object.defineProperty. */ -var isReadonlyPropertyBrowser = !goog.userAgent.IE || - goog.userAgent.isDocumentModeOrHigher(9); - - -function testIsReadonlyConfigurable() { - assertEquals( - fireauth.object.isReadonlyConfigurable_(), isReadonlyPropertyBrowser); -} - - -function testSetReadonlyProperty() { - var myObj = { - 'Argentina': 'Buenos Aires' - }; - fireauth.object.setReadonlyProperty(myObj, 'Bolivia', 'Sucre'); - - assertEquals('Buenos Aires', myObj['Argentina']); - assertEquals('Sucre', myObj['Bolivia']); -} - - -function testSetReadonlyProperty_overwrite() { - var myObj = {}; - - fireauth.object.setReadonlyProperty(myObj, 'Argentina', 'Moscow'); - assertEquals('Moscow', myObj['Argentina']); - - fireauth.object.setReadonlyProperty(myObj, 'Argentina', 'Buenos Aires'); - assertEquals('Buenos Aires', myObj['Argentina']); -} - - -function testSetReadonlyProperty_instance() { - var MyObj = function() { - fireauth.object.setReadonlyProperty(this, 'Bolivia', 'Sucre'); - }; - var myInstance = new MyObj(); - assertEquals('Sucre', myInstance['Bolivia']); -} - - -function testSetReadonlyProperty_isActuallyReadonly() { - if (!isReadonlyPropertyBrowser) { - return; - } - - var myObj = {}; - fireauth.object.setReadonlyProperty(myObj, 'Argentina', 'Buenos Aires'); - assertEquals('Buenos Aires', myObj['Argentina']); - myObj['Argentina'] = 'Toronto'; - assertEquals('Buenos Aires', myObj['Argentina']); -} - - -function testSetReadonlyProperties() { - var myObj = {}; - fireauth.object.setReadonlyProperties(myObj, { - 'Brazil': 'Brasilia', - 'Chile': 'Santiago' - }); - assertEquals('Brasilia', myObj['Brazil']); - assertEquals('Santiago', myObj['Chile']); -} - - -function testSetReadonlyProperties_isActuallyReadonly() { - if (!isReadonlyPropertyBrowser) { - return; - } - - var myObj = {}; - fireauth.object.setReadonlyProperties(myObj, { - 'Brazil': 'Brasilia', - 'Chile': 'Santiago' - }); - - myObj['Brazil'] = 'Beijing'; - myObj['Chile'] = 'Seoul'; - - assertEquals('Brasilia', myObj['Brazil']); - assertEquals('Santiago', myObj['Chile']); -} - - -function testMakeReadonlyCopy() { - var myObj = { - 'Brazil': 'Brasilia', - 'Chile': 'Santiago' - }; - assertObjectEquals(myObj, fireauth.object.makeReadonlyCopy(myObj)); -} - - -function testMakeReadonlyCopy_isActuallyReadonly() { - if (!isReadonlyPropertyBrowser) { - return; - } - - var myObj = { - 'Brazil': 'Brasilia', - 'Chile': 'Santiago' - }; - var copy = fireauth.object.makeReadonlyCopy(myObj); - copy['Brazil'] = 'Paris'; - copy['Chile'] = 'London'; - assertObjectEquals(myObj, copy); -} - - -function testMakeWritableCopy() { - var myObj = fireauth.object.makeReadonlyCopy({ - 'Brazil': 'Brasilia', - 'Chile': 'Santiago' - }); - assertObjectEquals(myObj, fireauth.object.makeWritableCopy(myObj)); -} - - -function testMakeWritableCopy_isActuallyWritable() { - var myObj = fireauth.object.makeReadonlyCopy({ - 'Brazil': 'Brasilia', - 'Chile': 'Paris' - }); - var copy = fireauth.object.makeWritableCopy(myObj); - copy['Chile'] = 'Santiago'; - assertObjectEquals('Santiago', copy['Chile']); -} - - -function testhasNonEmptyFields_true() { - var obj = {'a': 1, 'b': 2, 'c': 3}; - assertTrue(fireauth.object.hasNonEmptyFields(obj, ['a', 'c'])); -} - - -function testhasNonEmptyFields_false() { - var obj = {'a': 1, 'b': 2}; - assertFalse(fireauth.object.hasNonEmptyFields(obj, ['a', 'c'])); -} - - -function testhasNonEmptyFields_empty() { - var obj = {'a': 1, 'b': 2, 'c': 3}; - assertTrue(fireauth.object.hasNonEmptyFields(obj, [])); -} - - -function testhasNonEmptyFields_objectUndefined() { - assertFalse(fireauth.object.hasNonEmptyFields(undefined, ['a'])); -} - - -function testhasNonEmptyFields_fieldsUndefined() { - assertTrue(fireauth.object.hasNonEmptyFields({}, undefined)); -} - - -function testhasNonEmptyFields_objectHasUndefinedField() { - var obj = {'a': 1, 'b': 2, 'c': undefined}; - assertFalse(fireauth.object.hasNonEmptyFields(obj, ['a', 'c'])); -} - - -function testhasNonEmptyFields_objectHasNullField() { - var obj = {'a': null, 'b': 2, 'c': 3}; - assertFalse(fireauth.object.hasNonEmptyFields(obj, ['a', 'c'])); -} - - -function testhasNonEmptyFields_objectHasEmptyStringField() { - var obj = {'a': '', 'b': 'foo', 'c': 'bar'}; - assertFalse(fireauth.object.hasNonEmptyFields(obj, ['a', 'c'])); -} - - -function testhasNonEmptyFields_objectHasZeroField() { - var obj = {'a': 1, 'b': 2, 'c': 0}; - assertTrue(fireauth.object.hasNonEmptyFields(obj, ['a', 'c'])); -} - - -function testhasNonEmptyFields_objectHasFalseField() { - var obj = {'one': false, 'two': true, 'three': true}; - assertTrue(fireauth.object.hasNonEmptyFields(obj, ['one', 'three'])); -} - - -function testUnsafeCreateReadOnlyCopy() { - if (!isReadonlyPropertyBrowser) { - return; - } - - var myObj = { - 'a': [ - {'b': 1}, - 'str', - {} - ] - }; - // Create read-only copy. - var copy = fireauth.object.unsafeCreateReadOnlyCopy(myObj); - // Confirm object copied. - assertObjectEquals(myObj, copy); - assertEquals(1, copy['a'][0]['b']); - // This will have no effect. - copy['a'][0]['b'] = 2; - assertEquals(1, copy['a'][0]['b']); - // This should have no effect either. - copy['a'][0] = 2; - assertEquals(1, copy['a'][0]['b']); -} - - -function testUnsafeCreateReadOnlyCopy_nonCyclicalReferences() { - if (!isReadonlyPropertyBrowser) { - return; - } - - var c = {}; - // a depends on c. - var a = {'d': [c, 1]}; - var d = {'b': {'a': [0, a]}, 'c': c}; - var copy = fireauth.object.unsafeCreateReadOnlyCopy(d); - assertObjectEquals(d, copy); - // Should have no effect. - copy['c'] = null; - // Equal references copied. - assertObjectEquals(copy['c'], copy['b']['a'][1]['d'][0]); - - var e = { - 'f': c, - 'g': [c, c], - 'h': c - }; - var copy2 = fireauth.object.unsafeCreateReadOnlyCopy(e); - assertObjectEquals(e, copy2); - // Should have no effect. - copy2['f'] = 1; - // Equal references copied. - assertObjectEquals(copy2['f'], copy2['g'][0]); - assertObjectEquals(copy2['f'], copy2['h']); -} - - -function testSetDeprecatedReadonlyProperty() { - var spyLog = goog.testing.recordFunction(); - stubs.replace(fireauth.deprecation, 'log', spyLog); - - - var myObj = { - 'Argentina': 'Buenos Aires' - }; - var warning = /** @type {fireauth.deprecation.Deprecations} */ ( - 'Bolivia is deprecated.'); - fireauth.object.setDeprecatedReadonlyProperty(myObj, 'Bolivia', 'Sucre', - warning); - - // The warning should not be shown until Bolivia is referenced. - spyLog.assertCallCount(0); - - assertEquals('Buenos Aires', myObj['Argentina']); - spyLog.assertCallCount(0); - - assertEquals('Sucre', myObj['Bolivia']); - spyLog.assertCallCount(1); - assertEquals(warning, spyLog.getLastCall().getArgument(0)); - - assertEquals('Sucre', myObj['Bolivia']); - spyLog.assertCallCount(2); - assertEquals(warning, spyLog.getLastCall().getArgument(0)); - - assertEquals('Buenos Aires', myObj['Argentina']); - spyLog.assertCallCount(2); -} diff --git a/packages/auth/test/proactiverefresh_test.js b/packages/auth/test/proactiverefresh_test.js deleted file mode 100644 index dd3faf76880..00000000000 --- a/packages/auth/test/proactiverefresh_test.js +++ /dev/null @@ -1,495 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for proactiverefresh.js. - */ - -goog.provide('fireauth.ProactiveRefreshTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.ProactiveRefresh'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.ProactiveRefreshTest'); - - -var clock; -var stubs = new goog.testing.PropertyReplacer(); -var forceRetryError = false; -var operation = null; -var unrecoverableOperation = null; -var retryPolicy = null; -var getWaitDuration = null; -var proactiveRefresh = null; -var lowerBound = null; -var upperBound = null; -var runsInBackground = true; -var lastTimestamp = 0; -var makeAppVisible = null; -var cycle = 1000; - - -function setUp() { - // Whether to force a retry error to be thrown in the operation to be run. - forceRetryError = false; - // The time stamp corresponding to the last operation run. - lastTimestamp = 0; - // Initialize mock clock. - clock = new goog.testing.MockClock(true); - // Stub on app visible utility. - makeAppVisible = null; - stubs.replace( - fireauth.util, - 'onAppVisible', - function() { - return new goog.Promise(function(resolve, reject) { - // On every call, save onAppVisible resolution function. - makeAppVisible = resolve; - }); - }); - // Operation to practively refresh. - operation = goog.testing.recordFunction(function() { - // Record last run time. - lastTimestamp = Date.now(); - if (!forceRetryError) { - // Do not force error retry. Resolve successfully. - return goog.Promise.resolve(); - } else { - // If retrial error should be forced, throw a network error. - return goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.NETWORK_REQUEST_FAILED)); - } - }); - // Operation which throws an unrecoverable error. - unrecoverableOperation = goog.testing.recordFunction(function() { - // Record last run time. - lastTimestamp = Date.now(); - // Throw unrecoverable error. - return goog.Promise.reject( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)); - }); - // Retry policy. Retry only on network errors. - retryPolicy = function(error) { - if (error && error.code == 'auth/network-request-failed') { - return true; - } - return false; - }; - // Return a cycle for next time to rerun after a successful run. - getWaitDuration = function() { - return cycle; - }; - // Upper bound is 90% of a cycle. - upperBound = 0.9 * cycle; - // Lower bound is 10 % of a cycle. - lowerBound = 0.1 * cycle; - // Whether to run refresh in the background. - runsInBackground = true; -} - - -/** Simulates an app becoming visible. */ -function simulateAppIsVisible() { - // Simulate the app becoming visible. - makeAppVisible(); - // This is needed for the internal promises to resolve. - clock.tick(0.1); -} - - -/** - * Rounds the actual value and then asserts it is equal to the expected rounded - * value. - * @param {number} expected The expected value. - * @param {number} actual The actual value. - */ -function assertRoundedEqual(expected, actual) { - // Round to the nearest and then compare. - assertEquals(Math.floor(expected), Math.floor(actual)); -} - - -function tearDown() { - // Reset stubs, mocks and all global test variables. - stubs.reset(); - forceRetryError = false; - operation = null; - retryPolicy = null; - getWaitDuration = null; - upperBound = null; - lowerBound = null; - runsInBackground = true; - makeAppVisible = null; - unrecoverableOperation = null; - if (proactiveRefresh) { - proactiveRefresh.stop(); - proactiveRefresh = null; - } - lastTimestamp = 0; - goog.dispose(clock); -} - - -function testProactiveRefresh_invalidBounds() { - var error = assertThrows(function() { - new fireauth.ProactiveRefresh( - operation, retryPolicy, getWaitDuration, upperBound, lowerBound, - runsInBackground); - }); - assertEquals( - 'Proactive refresh lower bound greater than upper bound!', error.message); -} - - -function testProactiveRefresh_runsInBackground_success() { - // Test proactive refresh with multiple successful runs when the refresh can - // run in the background. - // Can run in background. - runsInBackground = true; - proactiveRefresh = new fireauth.ProactiveRefresh( - operation, retryPolicy, getWaitDuration, lowerBound, upperBound, - runsInBackground); - // Assert proactive refresh is not running. - assertFalse(proactiveRefresh.isRunning()); - // Start proactive refresh. - proactiveRefresh.start(); - // Assert proactive refresh is running. - assertTrue(proactiveRefresh.isRunning()); - // Simulate one cycle passed. - clock.tick(cycle); - // Confirm operation run after one cycle. - assertEquals(1, operation.getCallCount()); - assertEquals(cycle, lastTimestamp); - // Confirm operation run after 2 cycle. - clock.tick(cycle); - assertEquals(2, operation.getCallCount()); - assertEquals(2 * cycle, lastTimestamp); - // Confirm operation run after 3 cycles. - clock.tick(cycle); - assertEquals(3, operation.getCallCount()); - assertEquals(3 * cycle, lastTimestamp); - // Stop proactive refresh. - proactiveRefresh.stop(); - // Assert proactive refresh is not running. - assertFalse(proactiveRefresh.isRunning()); - // Confirm proactive refresh stopped even after another cycle. - clock.tick(cycle); - assertEquals(3, operation.getCallCount()); - - // Restart should reset and start again. - proactiveRefresh.start(); - // Assert proactive refresh is running. - assertTrue(proactiveRefresh.isRunning()); - // Simulate another cycle. - clock.tick(cycle); - // Operation should run again. - assertEquals(4, operation.getCallCount()); - assertEquals(5 * cycle, lastTimestamp); - // Stop proactive refresh - proactiveRefresh.stop(); - // Confirm proactive refresh stopped. - clock.tick(cycle); - assertEquals(4, operation.getCallCount()); -} - - -function testProactiveRefresh_cannotRunInBackground_success() { - // Test proactive refresh with multiple successful runs when the refresh - // cannot run in the background. - // Run while forcing refresh only when app is visible. - runsInBackground = false; - proactiveRefresh = new fireauth.ProactiveRefresh( - operation, retryPolicy, getWaitDuration, lowerBound, upperBound, - runsInBackground); - // Start proactive refresh. - proactiveRefresh.start(); - // Simulate 3.5 cycles passed. - clock.tick(3.5 * cycle); - // As app is not visible, operation should not run. - assertEquals(0, operation.getCallCount()); - // Simulate the app becoming visible. - simulateAppIsVisible(); - // Operation should run immediately at that point in time. - assertEquals(1, operation.getCallCount()); - assertRoundedEqual(3.5 * cycle, lastTimestamp); - // Simulate 1.5 cycles passed and then app is visible again. - clock.tick(1.5 * cycle); - simulateAppIsVisible(); - // Operation should run immediately. - assertEquals(2, operation.getCallCount()); - assertRoundedEqual(5 * cycle, lastTimestamp); - // Simulate app immediately visible on next run after the typical success - // interval. - clock.tick(1 * cycle); - simulateAppIsVisible(); - // Operation should run. - assertEquals(3, operation.getCallCount()); - assertRoundedEqual(6 * cycle, lastTimestamp); - // Stop refresh. - proactiveRefresh.stop(); - // Simulate another cycle passed. - clock.tick(1 * cycle); - // Even after app is visible, operation should not run again. - simulateAppIsVisible(); - // No additional run. - assertEquals(3, operation.getCallCount()); -} - - -function testProactiveRefresh_unrecoverableError() { - // Test proactive refresh when an error that does not meet retry policy is - // thrown. - // Can run in background. - runsInBackground = true; - // Test with an operation that throws an unrecoverable error. - proactiveRefresh = new fireauth.ProactiveRefresh( - unrecoverableOperation, retryPolicy, getWaitDuration, lowerBound, - upperBound, runsInBackground); - // Assert proactive refresh is not running. - assertFalse(proactiveRefresh.isRunning()); - // Start proactive refresh. - proactiveRefresh.start(); - // After 3 cycles, only one run should have been recorded. - clock.tick(3 * cycle); - // Should run once, fail and never run again. - assertEquals(1, unrecoverableOperation.getCallCount()); - // Operation should only run after first cycle. - assertEquals(cycle, lastTimestamp); - // Assert proactive refresh is running. - assertTrue(proactiveRefresh.isRunning()); - // Stop proactive refresh. - proactiveRefresh.stop(); - // Assert proactive refresh is not running. - assertFalse(proactiveRefresh.isRunning()); -} - - -function testProactiveRefresh_runsInBackground_retryPolicy() { - // Test exponential backoff after a network error when the refresh can run in - // the background. - // Can run in background. - runsInBackground = true; - proactiveRefresh = new fireauth.ProactiveRefresh( - operation, retryPolicy, getWaitDuration, lowerBound, upperBound, - runsInBackground); - // Assert proactive refresh is not running. - assertFalse(proactiveRefresh.isRunning()); - // Start proactive refresh. - proactiveRefresh.start(); - clock.tick(1 * cycle); - // Should run once after a cycle. - assertEquals(1, operation.getCallCount()); - assertEquals(cycle, lastTimestamp); - // Simulate error that meets retry policy. - forceRetryError = true; - // Wait for one cycle. - clock.tick(1 * cycle); - // Operation should run after one cycle. - assertEquals(2, operation.getCallCount()); - assertEquals(2 * cycle, lastTimestamp); - // As error that meets retry policy detected, this should rerun at lower bound - // interval. - clock.tick(0.1 * cycle); - // Rerun after 0.1 cycles. - assertEquals(3, operation.getCallCount()); - assertEquals(2.1 * cycle, lastTimestamp); - // Should run again in 0.2 cycles. - clock.tick(0.2 * cycle); - assertEquals(4, operation.getCallCount()); - assertEquals(2.3 * cycle, lastTimestamp); - // Should run again in 0.4 cycles. - clock.tick(0.4 * cycle); - assertEquals(5, operation.getCallCount()); - assertEquals(2.7 * cycle, lastTimestamp); - // Should run again in 0.8 cycles. - clock.tick(0.8 * cycle); - assertEquals(6, operation.getCallCount()); - assertEquals(3.5 * cycle, lastTimestamp); - // Should reach upper bound of 0.9. - clock.tick(0.9 * cycle); - // Reruns at upper bound interval. - assertEquals(7, operation.getCallCount()); - assertEquals(4.4 * cycle, lastTimestamp); - // Should not exceed upper bound. - clock.tick(0.9 * cycle); - // Reruns again at upper bound interval. - assertEquals(8, operation.getCallCount()); - assertEquals(5.3 * cycle, lastTimestamp); - // Assert proactive refresh is running. - assertTrue(proactiveRefresh.isRunning()); - // Simulate success on next run. - forceRetryError = false; - // Next rerun at upper bound interval should succeed. - clock.tick(0.9 * cycle); - assertEquals(9, operation.getCallCount()); - assertEquals(6.2 * cycle, lastTimestamp); - // Next one should run at normal cycles. - clock.tick(cycle); - assertEquals(10, operation.getCallCount()); - assertEquals(7.2 * cycle, lastTimestamp); - // Assert proactive refresh is running. - assertTrue(proactiveRefresh.isRunning()); - // Stop proactive refresh. - proactiveRefresh.stop(); - // Assert proactive refresh is not running. - assertFalse(proactiveRefresh.isRunning()); -} - - -function testProactiveRefresh_equalBounds() { - // Check when upper and lower bounds are equal that the same wait is applied - // each time an error occurs. - runsInBackground = true; - // Use same upper/lower bound. - proactiveRefresh = new fireauth.ProactiveRefresh( - operation, retryPolicy, getWaitDuration, lowerBound, lowerBound, - runsInBackground); - // Simulate error that meets retry policy. - forceRetryError = true; - // Start proactive refresh. - proactiveRefresh.start(); - // Operation should run after one cycle with an error occurring. - clock.tick(1 * cycle); - // Should run once. - assertEquals(1, operation.getCallCount()); - assertEquals(cycle, lastTimestamp); - // Simulate one lower bound duration. - clock.tick(lowerBound); - // Should run again. - assertEquals(2, operation.getCallCount()); - assertEquals(cycle + lowerBound, lastTimestamp); - // Simulate another lower bound duration. - clock.tick(lowerBound); - // Should run again. - assertEquals(3, operation.getCallCount()); - assertEquals(cycle + 2 * lowerBound, lastTimestamp); - // Simulate another lower bound duration. - clock.tick(lowerBound); - // Should run again. - assertEquals(4, operation.getCallCount()); - assertEquals(cycle + 3 * lowerBound, lastTimestamp); - // Stop proactive refresh. - proactiveRefresh.stop(); -} - - -function testProactiveRefresh_cannotRunInBackground_retryPolicy() { - // Test exponential backoff after a network error when the refresh cannot run - // in the background. - // Can run in background. - runsInBackground = false; - proactiveRefresh = new fireauth.ProactiveRefresh( - operation, retryPolicy, getWaitDuration, lowerBound, upperBound, - runsInBackground); - // Simulate error that meets retry policy. - forceRetryError = true; - // Start proactive refresh. - proactiveRefresh.start(); - // Operation should run after one cycle if app is visible. - clock.tick(1 * cycle); - // Simulate the app becoming visible. - simulateAppIsVisible(); - // Should run once. - assertEquals(1, operation.getCallCount()); - assertEquals(cycle, lastTimestamp); - // Simulate 3 cycles before app is visible again. - clock.tick(3 * cycle); - // Simulate the app becoming visible. - simulateAppIsVisible(); - // Even though an error occurred, it won't run until app is visible again. - assertEquals(2, operation.getCallCount()); - assertRoundedEqual(4 * cycle, lastTimestamp); - // Simulate 3 cycles before app is visible again. - clock.tick(3 * cycle); - // Simulate the app becoming visible. - simulateAppIsVisible(); - // Even though an error occurred, it won't run until app is visible again. - assertEquals(3, operation.getCallCount()); - assertRoundedEqual(7 * cycle, lastTimestamp); - // Simulate 0.4 cycles before app is visible again. This is the expected time - // to rerun if the app is visible and 2 errors already occurred. - clock.tick(0.4 * cycle); - // Simulate the app becoming visible. - simulateAppIsVisible(); - // Next run occurs at expected time as app is visible. - assertEquals(4, operation.getCallCount()); - assertRoundedEqual(7.4 * cycle, lastTimestamp); - // Simulate 0.8 cycles and app is visible. The is the expected next run after - // 3 errors. - clock.tick(0.8 * cycle); - // Simulate the app becoming visible. - simulateAppIsVisible(); - // Next run occurs at expected time as app is visible. - assertEquals(5, operation.getCallCount()); - assertRoundedEqual(8.2 * cycle, lastTimestamp); - // Simulate next operation succeeds after expected retry interval. - forceRetryError = false; - clock.tick(0.9 * cycle); - // Simulate the app becoming visible. - simulateAppIsVisible(); - // Should have succeeded after 0.9 cycles. - assertEquals(6, operation.getCallCount()); - assertRoundedEqual(9.1 * cycle, lastTimestamp); - // Next run should occur as scheduled after a successful refresh. - clock.tick(cycle); - // Simulate the app becoming visible. - simulateAppIsVisible(); - assertEquals(7, operation.getCallCount()); - assertRoundedEqual(10.1 * cycle, lastTimestamp); - // Stop proactive refresh. - proactiveRefresh.stop(); -} - - -function testProactiveRefresh_runsInBackground_retryPolicy_stopAndRestart() { - // Test exponential backoff after a network error when the refresh can run in - // the background. Test that restart resets the previous error. - // Can run in background. - runsInBackground = true; - proactiveRefresh = new fireauth.ProactiveRefresh( - operation, retryPolicy, getWaitDuration, lowerBound, upperBound, - runsInBackground); - // Simulate error that meets retry policy. - forceRetryError = true; - // Start proactive refresh. - proactiveRefresh.start(); - // After once cycle, operation should run and error detected. - clock.tick(cycle); - assertEquals(1, operation.getCallCount()); - assertEquals(cycle, lastTimestamp); - // Should rerun at lower bound. - clock.tick(0.1 * cycle); - assertEquals(2, operation.getCallCount()); - assertEquals(1.1 * cycle, lastTimestamp); - // Stop proactive refresh. - proactiveRefresh.stop(); - // Restart proactive refresh. - proactiveRefresh.start(); - // Confirm next run is at regular interval regardless of previous error. - clock.tick(cycle); - assertEquals(3, operation.getCallCount()); - assertEquals(2.1 * cycle, lastTimestamp); - // Stop proactive refresh. - proactiveRefresh.stop(); -} diff --git a/packages/auth/test/recaptchaverifier/grecaptchamock_test.js b/packages/auth/test/recaptchaverifier/grecaptchamock_test.js deleted file mode 100644 index 855dae2b762..00000000000 --- a/packages/auth/test/recaptchaverifier/grecaptchamock_test.js +++ /dev/null @@ -1,553 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -/** - * @fileoverview Tests for grecaptchamock.js. - */ - -goog.provide('fireauth.GRecaptchaMockFactoryTest'); - -goog.require('fireauth.GRecaptchaMockFactory'); -goog.require('fireauth.RecaptchaMock'); -goog.require('fireauth.util'); -goog.require('goog.dom'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.events'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.GRecaptchaMockFactoryTest'); - - -var stubs = new goog.testing.PropertyReplacer(); -var clock; -var randomCounter; -var myElement, myElement2; -var startInstanceId = fireauth.GRecaptchaMockFactory.START_INSTANCE_ID; -var expirationTimeMs = fireauth.GRecaptchaMockFactory.EXPIRATION_TIME_MS; -var solveTimeMs = fireauth.GRecaptchaMockFactory.SOLVE_TIME_MS; - - -function setUp() { - // Create DIV test element and add to document. - myElement = goog.dom.createDom(goog.dom.TagName.DIV, {'id': 'recaptcha'}); - document.body.appendChild(myElement); - // Create another DIV test element and add to document. - myElement2 = goog.dom.createDom(goog.dom.TagName.DIV, {'id': 'recaptcha2'}); - document.body.appendChild(myElement2); - randomCounter = 0; - // Install mock clock. - clock = new goog.testing.MockClock(true); - stubs.replace( - fireauth.util, - 'generateRandomAlphaNumericString', - function(charCount) { - assertEquals(50, charCount); - return 'random' + (randomCounter++); - }); -} - - -function tearDown() { - // Destroy both elements. - if (myElement) { - goog.dom.removeNode(myElement); - myElement = null; - } - if (myElement2) { - goog.dom.removeNode(myElement2); - myElement2 = null; - } - goog.dispose(clock); - stubs.reset(); -} - - -function testRecaptchaMock_visible() { - var responseCallback = goog.testing.recordFunction(); - var expiredCallback = goog.testing.recordFunction(); - var mockInstance = new fireauth.RecaptchaMock('recaptcha', { - 'sitekey': 'SITE_KEY1', - 'size': 'normal', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback, - 'expired-callback': expiredCallback - }); - // No response initially. - assertNull(mockInstance.getResponse()); - assertEquals(0, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - - // Should auto-solve after some delay. - clock.tick(solveTimeMs); - assertEquals('random0', mockInstance.getResponse()); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - clock.tick(expirationTimeMs - 1); - assertEquals('random0', mockInstance.getResponse()); - - // Simulate token expired. - clock.tick(1); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertNull(mockInstance.getResponse()); - - // Should auto-solve again after delay. - clock.tick(solveTimeMs); - assertEquals('random1', mockInstance.getResponse()); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - // Delete should result in APIs throwing expected error. - mockInstance.delete(); - assertThrows(function() { - mockInstance.getResponse(); - }); - assertThrows(function() { - mockInstance.delete(); - }); -} - - -function testRecaptchaMock_invisible() { - var responseCallback = goog.testing.recordFunction(); - var expiredCallback = goog.testing.recordFunction(); - var mockInstance = new fireauth.RecaptchaMock('recaptcha', { - 'sitekey': 'SITE_KEY1', - 'size': 'invisible', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback, - 'expired-callback': expiredCallback - }); - // Should not trigger until execute is triggered. - clock.tick(100000); - assertNull(mockInstance.getResponse()); - - // Execute should auto-solve after some delay. - mockInstance.execute(); - assertNull(mockInstance.getResponse()); - assertEquals(0, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - clock.tick(solveTimeMs); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - assertEquals('random0', mockInstance.getResponse()); - clock.tick(expirationTimeMs - 1); - assertEquals('random0', mockInstance.getResponse()); - - // On expiration, response should be nullified and expected callback - // triggered. - clock.tick(1); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - // Should not trigger until execute is triggered. - clock.tick(100000); - assertNull(mockInstance.getResponse()); - - // Click should also trigger execute. - goog.testing.events.fireClickSequence(myElement); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertNull(mockInstance.getResponse()); - clock.tick(solveTimeMs); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertEquals('random1', mockInstance.getResponse()); - - // Since response is not expired, these calls should do nothing. - goog.testing.events.fireClickSequence(myElement); - mockInstance.execute(); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertEquals('random1', mockInstance.getResponse()); - - // Delete should result in APIs throwing expected error. - mockInstance.delete(); - assertThrows(function() { - mockInstance.getResponse(); - }); - assertThrows(function() { - mockInstance.execute(); - }); - assertThrows(function() { - mockInstance.delete(); - }); - // Click handler should no longer trigger execute and therefore should not - // throw an error. - assertNotThrows(function() { - goog.testing.events.fireClickSequence(myElement); - }); -} - - -function testGRecaptchaMockFactory_visible() { - var responseCallback = goog.testing.recordFunction(); - var expiredCallback = goog.testing.recordFunction(); - var mockFactory = new fireauth.GRecaptchaMockFactory(); - var id1 = mockFactory.render('recaptcha', { - 'sitekey': 'SITE_KEY1', - 'size': 'normal', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback, - 'expired-callback': expiredCallback - }); - // Initially no response should be available. - assertNull(mockFactory.getResponse(id1)); - assertEquals(0, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - - // Instance should auto-solve after some delay. - clock.tick(solveTimeMs); - assertEquals('random0', mockFactory.getResponse(id1)); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - clock.tick(expirationTimeMs - 1); - assertEquals('random0', mockFactory.getResponse(id1)); - - // On expiration, response should be nullified and expired-callback triggered. - clock.tick(1); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertNull(mockFactory.getResponse(id1)); - - // Instance should auto-solve after some delay. - clock.tick(solveTimeMs); - assertEquals('random1', mockFactory.getResponse(id1)); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - mockFactory.reset(id1); - assertNull(mockFactory.getResponse(id1)); - - // On reset, instance should no longer auto-solve or trigger callbacks. - clock.tick(100000); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - - // Non-existing ID. - assertNull(mockFactory.getResponse(id1 + 1)); - assertNotThrows(function() { - mockFactory.reset(id1 + 1); - }); -} - - -function testGRecaptchaMockFactory_invisible_executeViaManualCall() { - var responseCallback = goog.testing.recordFunction(); - var expiredCallback = goog.testing.recordFunction(); - var mockFactory = new fireauth.GRecaptchaMockFactory(); - var id1 = mockFactory.render('recaptcha', { - 'sitekey': 'SITE_KEY1', - 'size': 'invisible', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback, - 'expired-callback': expiredCallback - }); - // Should not trigger until execute is triggered. - clock.tick(100000); - assertNull(mockFactory.getResponse(id1)); - - // execute should auto-solve. - mockFactory.execute(id1); - assertNull(mockFactory.getResponse(id1)); - assertEquals(0, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - clock.tick(solveTimeMs); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - assertEquals('random0', mockFactory.getResponse(id1)); - clock.tick(expirationTimeMs - 1); - assertEquals('random0', mockFactory.getResponse(id1)); - - // On expiration, response should be nullified and expired-callback triggered. - clock.tick(1); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - // Should not trigger until execute is triggered. - clock.tick(100000); - assertNull(mockFactory.getResponse(id1)); - - // execute should auto-solve. - mockFactory.execute(id1); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertNull(mockFactory.getResponse(id1)); - clock.tick(solveTimeMs); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertEquals('random1', mockFactory.getResponse(id1)); - - // reset should nullify responses. - mockFactory.reset(id1); - assertNull(mockFactory.getResponse(id1)); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); -} - - -function testGRecaptchaMockFactory_invisible_executeViaClick() { - var responseCallback = goog.testing.recordFunction(); - var expiredCallback = goog.testing.recordFunction(); - var mockFactory = new fireauth.GRecaptchaMockFactory(); - var id1 = mockFactory.render('recaptcha', { - 'sitekey': 'SITE_KEY1', - 'size': 'invisible', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback, - 'expired-callback': expiredCallback - }); - // Should not trigger until execute is triggered. - clock.tick(100000); - assertNull(mockFactory.getResponse(id1)); - - // Instance should auto-solve on click after some delay. - goog.testing.events.fireClickSequence(myElement); - assertNull(mockFactory.getResponse(id1)); - assertEquals(0, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - clock.tick(solveTimeMs); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(0, expiredCallback.getCallCount()); - assertEquals('random0', mockFactory.getResponse(id1)); - clock.tick(expirationTimeMs - 1); - assertEquals('random0', mockFactory.getResponse(id1)); - - // On expiration, response should be nullified and expired-callback triggered. - clock.tick(1); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - // Should not trigger until execute is triggered. - clock.tick(100000); - assertNull(mockFactory.getResponse(id1)); - - // Instance should auto-solve on click after some delay. - goog.testing.events.fireClickSequence(myElement); - assertEquals(1, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertNull(mockFactory.getResponse(id1)); - clock.tick(solveTimeMs); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); - assertEquals('random1', mockFactory.getResponse(id1)); - - // On reset, instance should no longer auto-solve or trigger callbacks. - mockFactory.reset(id1); - assertNull(mockFactory.getResponse(id1)); - goog.testing.events.fireClickSequence(myElement); - clock.tick(100000); - assertNull(mockFactory.getResponse(id1)); - assertEquals(2, responseCallback.getCallCount()); - assertEquals(1, expiredCallback.getCallCount()); -} - - -function testGRecaptchaMockFactory_visible_multipleInstances() { - var responseCallback1 = goog.testing.recordFunction(); - var expiredCallback1 = goog.testing.recordFunction(); - var responseCallback2 = goog.testing.recordFunction(); - var expiredCallback2 = goog.testing.recordFunction(); - - var mockFactory = new fireauth.GRecaptchaMockFactory(); - var id1 = mockFactory.render('recaptcha', { - 'sitekey': 'SITE_KEY1', - 'size': 'normal', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback1, - 'expired-callback': expiredCallback1 - }); - var id2 = mockFactory.render(myElement2, { - 'sitekey': 'SITE_KEY2', - 'size': 'normal', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback2, - 'expired-callback': expiredCallback2 - }); - // Confirm expected instance IDs. - assertEquals(id1, startInstanceId); - assertEquals(id2, startInstanceId + 1); - - // Initially no response should be available. - assertNull(mockFactory.getResponse(id1)); - assertNull(mockFactory.getResponse(id2)); - assertEquals(0, responseCallback1.getCallCount()); - assertEquals(0, expiredCallback1.getCallCount()); - assertEquals(0, responseCallback2.getCallCount()); - assertEquals(0, expiredCallback2.getCallCount()); - - // Instances should auto-solve after some delay. - clock.tick(solveTimeMs); - assertEquals('random0', mockFactory.getResponse(id1)); - assertEquals(1, responseCallback1.getCallCount()); - assertEquals(0, expiredCallback1.getCallCount()); - assertEquals('random1', mockFactory.getResponse(id2)); - assertEquals(1, responseCallback2.getCallCount()); - assertEquals(0, expiredCallback2.getCallCount()); - - // When no ID provided, the first instance should be used. - assertEquals('random0', mockFactory.getResponse(id1)); - - // On expiration, response should be nullified and expired-callback triggered. - clock.tick(expirationTimeMs); - assertEquals(1, responseCallback1.getCallCount()); - assertEquals(1, expiredCallback1.getCallCount()); - assertNull(mockFactory.getResponse(id1)); - assertEquals(1, responseCallback2.getCallCount()); - assertEquals(1, expiredCallback2.getCallCount()); - assertNull(mockFactory.getResponse(id2)); - - // Resetting one instance should not affect the other. - mockFactory.reset(id2); - clock.tick(solveTimeMs); - assertEquals('random2', mockFactory.getResponse(id1)); - assertEquals(2, responseCallback1.getCallCount()); - assertEquals(1, expiredCallback1.getCallCount()); - assertNull(mockFactory.getResponse(id2)); - assertEquals(1, responseCallback2.getCallCount()); - assertEquals(1, expiredCallback2.getCallCount()); - - // When no ID provided, the first instance is used by default. - mockFactory.reset(); - clock.tick(solveTimeMs); - assertNull(mockFactory.getResponse(id1)); - assertEquals(2, responseCallback1.getCallCount()); - assertEquals(1, expiredCallback1.getCallCount()); -} - - -function testGRecaptchaMockFactory_invisible_multipleInstances() { - var responseCallback1 = goog.testing.recordFunction(); - var expiredCallback1 = goog.testing.recordFunction(); - var responseCallback2 = goog.testing.recordFunction(); - var expiredCallback2 = goog.testing.recordFunction(); - - var mockFactory = new fireauth.GRecaptchaMockFactory(); - var id1 = mockFactory.render('recaptcha', { - 'sitekey': 'SITE_KEY1', - 'size': 'invisible', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback1, - 'expired-callback': expiredCallback1 - }); - var id2 = mockFactory.render(myElement2, { - 'sitekey': 'SITE_KEY2', - 'size': 'invisible', - 'theme': 'dark ', - 'type': 'audio', - 'callback': responseCallback2, - 'expired-callback': expiredCallback2 - }); - - // Confirm expected instance IDs. - assertEquals(id1, startInstanceId); - assertEquals(id2, startInstanceId + 1); - - // Initially no response should be available. - assertNull(mockFactory.getResponse(id1)); - assertNull(mockFactory.getResponse(id2)); - assertEquals(0, responseCallback1.getCallCount()); - assertEquals(0, expiredCallback1.getCallCount()); - assertEquals(0, responseCallback2.getCallCount()); - assertEquals(0, expiredCallback2.getCallCount()); - - // Invisible reCAPTCHAs should not auto-solve without click or execute call. - clock.tick(100000); - assertEquals(0, responseCallback1.getCallCount()); - assertEquals(0, expiredCallback1.getCallCount()); - assertEquals(0, responseCallback2.getCallCount()); - assertEquals(0, expiredCallback2.getCallCount()); - - // Corresponding instance should auto-solve on click without affecting other - // instance. - goog.testing.events.fireClickSequence(myElement); - clock.tick(solveTimeMs); - assertEquals('random0', mockFactory.getResponse(id1)); - assertEquals(1, responseCallback1.getCallCount()); - assertEquals(0, expiredCallback1.getCallCount()); - assertNull(mockFactory.getResponse(id2)); - assertEquals(0, responseCallback2.getCallCount()); - assertEquals(0, expiredCallback2.getCallCount()); - - // Second click should be ignored while there is a valid token. - goog.testing.events.fireClickSequence(myElement); - assertEquals('random0', mockFactory.getResponse(id1)); - assertEquals(1, responseCallback1.getCallCount()); - assertEquals(0, expiredCallback1.getCallCount()); - - // execute should be ignored while there is a valid token. - // When no ID is passed, the first instance created is used. - mockFactory.execute(); - assertEquals('random0', mockFactory.getResponse(id1)); - assertEquals(1, responseCallback1.getCallCount()); - assertEquals(0, expiredCallback1.getCallCount()); - - // Corresponding instance should auto-solve on click without affecting other - // instance. - goog.testing.events.fireClickSequence(myElement2); - clock.tick(solveTimeMs); - assertEquals('random0', mockFactory.getResponse(id1)); - assertEquals(1, responseCallback1.getCallCount()); - assertEquals(0, expiredCallback1.getCallCount()); - assertEquals('random1', mockFactory.getResponse(id2)); - assertEquals(1, responseCallback2.getCallCount()); - assertEquals(0, expiredCallback2.getCallCount()); - - // Expire only first instance. Second instance should not be affected. - clock.tick(expirationTimeMs - solveTimeMs); - assertNull(mockFactory.getResponse(id1)); - assertEquals(1, responseCallback1.getCallCount()); - assertEquals(1, expiredCallback1.getCallCount()); - assertEquals('random1', mockFactory.getResponse(id2)); - assertEquals(1, responseCallback2.getCallCount()); - assertEquals(0, expiredCallback2.getCallCount()); - - // Expire only second instance. First instance should not be affected. - clock.tick(solveTimeMs); - assertNull(mockFactory.getResponse(id2)); - assertEquals(1, responseCallback2.getCallCount()); - assertEquals(1, expiredCallback2.getCallCount()); - - // Reset first instance. Clicks should no longer trigger response. - mockFactory.reset(id1); - goog.testing.events.fireClickSequence(myElement); - clock.tick(solveTimeMs); - assertNull(mockFactory.getResponse(id1)); - assertEquals(1, responseCallback1.getCallCount()); - assertEquals(1, expiredCallback1.getCallCount()); - - // Click on second instance should solve. - goog.testing.events.fireClickSequence(myElement2); - clock.tick(solveTimeMs); - assertEquals('random2', mockFactory.getResponse(id2)); - assertEquals(2, responseCallback2.getCallCount()); - assertEquals(1, expiredCallback2.getCallCount()); - - // Reset second instance. Clicks should no longer trigger response. - mockFactory.reset(id2); - goog.testing.events.fireClickSequence(myElement2); - clock.tick(solveTimeMs); - assertNull(mockFactory.getResponse(id2)); - assertEquals(2, responseCallback2.getCallCount()); - assertEquals(1, expiredCallback2.getCallCount()); -} diff --git a/packages/auth/test/recaptchaverifier/mockloader_test.js b/packages/auth/test/recaptchaverifier/mockloader_test.js deleted file mode 100644 index ae98d7dd431..00000000000 --- a/packages/auth/test/recaptchaverifier/mockloader_test.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for mockloader.js. - */ - -goog.provide('fireauth.RecaptchaMockLoaderTest'); - -goog.require('fireauth.GRecaptchaMockFactory'); -goog.require('fireauth.RecaptchaMockLoader'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.RecaptchaMockLoaderTest'); - - -function testRecaptchaMockLoader_loadRecaptchaDeps() { - var mockLoader = new fireauth.RecaptchaMockLoader(); - return mockLoader.loadRecaptchaDeps().then(function(grecaptcha) { - assertEquals( - fireauth.GRecaptchaMockFactory.getInstance(), - grecaptcha); - }); -} - - -function testRecaptchaMockLoader_clearSingleRecaptcha() { - assertNotThrows(function() { - var mockLoader = new fireauth.RecaptchaMockLoader(); - mockLoader.clearSingleRecaptcha(); - }); -} diff --git a/packages/auth/test/recaptchaverifier/realloader_test.js b/packages/auth/test/recaptchaverifier/realloader_test.js deleted file mode 100644 index 3969e5165e4..00000000000 --- a/packages/auth/test/recaptchaverifier/realloader_test.js +++ /dev/null @@ -1,349 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Tests for realloader.js. - */ - -goog.provide('fireauth.RecaptchaRealLoaderTest'); - -goog.require('fireauth.RecaptchaRealLoader'); -goog.require('goog.Promise'); -goog.require('goog.Uri'); -goog.require('goog.dom'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.TestCase'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.mockmatchers'); - -goog.setTestOnly('fireauth.RecaptchaRealLoaderTest'); - - -var mockControl; -var stubs = new goog.testing.PropertyReplacer(); -var grecaptcha; -var myElement, myElement2; -var ignoreArgument; -var loaderInstance; - - -/** - * Initialize reCAPTCHA mocks. This mocks the grecaptcha library. - */ -function initializeRecaptchaMocks() { - var recaptcha = { - // Recaptcha challenge ID. - 'challengesId': 0, - // Recaptcha array of instances. - 'instances': [], - // Render reCAPTCHA instance. - 'render': function(container, parameters) { - // New widget ID. - var id = recaptcha.instances.length; - // Element container. - var ele = goog.dom.getElement(container); - // Store new reCAPTCHA instance and its parameters. - recaptcha.instances.push({ - 'container': container, - 'response': 'response-' + recaptcha.challengesId, - 'userResponse': '', - 'parameters': parameters, - 'callback': parameters['callback'] || null, - 'expired-callback': parameters['expired-callback'] || null, - 'execute': false - }); - // Increment challenges ID. - recaptcha.challengesId++; - if (parameters.size !== 'invisible') { - // recaptcha can only be rendered on an empty element. - assertFalse(goog.dom.getChildren(ele).length > 0); - // Fill container with some HTML to simulate rendered widget. - ele.innerHTML = '
'; - } - // Return the reCAPTCHA widget ID. - return id; - }, - // Reset reCAPTCHA instance - 'reset': function(opt_id) { - // If widget ID not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - var parameters = recaptcha.instances[id]['parameters']; - // Reset instance challenge and its other properties. - recaptcha.instances[id] = { - 'container': parameters['container'], - 'response': 'response-' + recaptcha.challengesId, - 'userResponse': '', - 'parameters': parameters, - 'callback': parameters['callback'], - 'expired-callback': parameters['expired-callback'], - 'execute': false - }; - // Increment challenges ID. - recaptcha.challengesId++; - }, - // Returns reCAPTCHA instance's user response. - 'getResponse': function(opt_id) { - // If widget ID not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - // Return user response. - return recaptcha.instances[id]['userResponse'] || ''; - }, - // Executes the invisible reCAPTCHA. This will either force a reCAPTCHA - // visible challenge or resolve immediately. For testing, the former - // scenario is used. - 'execute': function(opt_id) { - // If widget id not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - var instance = recaptcha.instances[id]; - var parameters = instance['parameters']; - // execute should not be called on a visible reCAPTCHA. - if (parameters['size'] !== 'invisible') { - throw new Error('execute called on visible reCAPTCHA!'); - } - // Mark execute flag as true. - instance['execute'] = true; - }, - // For internal testing, simulates the reCAPTCHA corresponding to ID passed - // is solved. - 'solveResponse': function(opt_id) { - // If widget ID not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - var instance = recaptcha.instances[id]; - var parameters = instance['parameters']; - // Updated user response with the solve response. - instance['userResponse'] = instance['response']; - // execute must have been called on invisible reCAPTCHA. - if (!instance['execute'] && parameters['size'] === 'invisible') { - throw new Error('execute needs to be called before solving response!'); - } - // Trigger reCAPTCHA callback. - if (instance['callback'] && - typeof instance['callback'] == 'function') { - instance['callback'](instance['response']); - } - // Update next challenge response. - instance['response'] = 'response-' + recaptcha.challengesId; - recaptcha.challengesId++; - }, - // For internal testing, simulates the reCAPTCHA token corresponding to ID - // passed is expired. - 'expireResponse': function(opt_id) { - // If widget ID not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - var instance = recaptcha.instances[id]; - // Reset user response. - instance['userResponse'] = ''; - // Trigger expired callback. - if (instance['expired-callback'] && - typeof instance['expired-callback'] == 'function') { - instance['expired-callback'](); - } - // Reset execute. - instance['execute'] = false; - } - }; - // Fake the Recaptcha global object. - goog.global['grecaptcha'] = recaptcha; -} - - -function setUp() { - mockControl = new goog.testing.MockControl(); - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl.$resetAll(); - // Create DIV test element and add to document. - myElement = goog.dom.createDom(goog.dom.TagName.DIV, {'id': 'recaptcha'}); - document.body.appendChild(myElement); - // Create another DIV test element and add to document. - myElement2 = goog.dom.createDom(goog.dom.TagName.DIV, {'id': 'recaptcha2'}); - document.body.appendChild(myElement2); - // Bypass singleton for tests so loaders are not shared among different tests. - loaderInstance = new fireauth.RecaptchaRealLoader(); - stubs.replace( - fireauth.RecaptchaRealLoader, - 'getInstance', - function() { - return loaderInstance; - }); -} - - -function tearDown() { - // Destroy both elements. - if (myElement) { - goog.dom.removeNode(myElement); - myElement = null; - } - if (myElement2) { - goog.dom.removeNode(myElement2); - myElement2 = null; - } - // Reset global grecaptcha. - grecaptcha = null; - delete grecaptcha; - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } - stubs.reset(); -} - - -/** - * Install the test to run and runs it. - * @param {string} id The test identifier. - * @param {function():!goog.Promise} func The test function to run. - * @return {!goog.Promise} The result of the test. - */ -function installAndRunTest(id, func) { - var testCase = new goog.testing.TestCase(); - testCase.addNewTest(id, func); - var error = null; - return testCase.runTestsReturningPromise().then(function(result) { - assertTrue(result.complete); - // Display error detected. - if (result.errors.length) { - fail(result.errors.join('\n')); - } - assertEquals(1, result.totalCount); - assertEquals(1, result.runCount); - assertEquals(1, result.successCount); - assertEquals(0, result.errors.length); - }).thenCatch(function(err) { - error = err; - }).then(function() { - if (error) { - throw error; - } - }); -} - - -function testRecaptchaRealLoader() { - return installAndRunTest('testRecaptchaRealLoader', function() { - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image' - }; - var currentLanguageCode = null; - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - // First load. Dependency loaded with null language code. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('', uri.getParameterValue('hl')); - currentLanguageCode = null; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - // Third load. Dependency loaded with 'fr' language code. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('fr', uri.getParameterValue('hl')); - currentLanguageCode = 'fr'; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - // Seventh load. Dependency loaded with 'ar' language code. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('ar', uri.getParameterValue('hl')); - currentLanguageCode = 'ar'; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - mockControl.$replayAll(); - var loader = new fireauth.RecaptchaRealLoader(); - // First load with empty string hl. - return loader.loadRecaptchaDeps(null).then(function() { - assertNull(currentLanguageCode); - // No load needed. - return loader.loadRecaptchaDeps(null); - }).then(function() { - assertNull(currentLanguageCode); - // Will load. - return loader.loadRecaptchaDeps('fr'); - }).then(function() { - assertEquals('fr', currentLanguageCode); - // Simulate reCAPTCHAs rendered. - grecaptcha.render(myElement, expectedParams); - grecaptcha.render(myElement2, expectedParams); - // This will do nothing. - return loader.loadRecaptchaDeps('de'); - }).then(function() { - assertEquals('fr', currentLanguageCode); - loader.clearSingleRecaptcha(); - // Still no load as one instance remains. - return loader.loadRecaptchaDeps('ru'); - }).then(function() { - assertEquals('fr', currentLanguageCode); - loader.clearSingleRecaptcha(); - // Language still loaded. No reload needed. - return loader.loadRecaptchaDeps('fr'); - }).then(function() { - assertEquals('fr', currentLanguageCode); - // This will load reCAPTCHA dependencies for specified language. - return loader.loadRecaptchaDeps('ar'); - }).then(function() { - assertEquals('ar', currentLanguageCode); - }); - }); -} - - -function testRecaptchaRealLoader_alreadyLoaded() { - return installAndRunTest( - 'testRecaptchaRealLoader_alreadyLoaded', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - // No dependency load. - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - safeLoad(ignoreArgument).$times(0); - mockControl.$replayAll(); - - // Initialize after simulated reCAPTCHA dependency is loaded. - var loader = new fireauth.RecaptchaRealLoader(); - return loader.loadRecaptchaDeps('de'); - }); -} diff --git a/packages/auth/test/recaptchaverifier/recaptchaverifier_test.js b/packages/auth/test/recaptchaverifier/recaptchaverifier_test.js deleted file mode 100644 index 25b8761b7e5..00000000000 --- a/packages/auth/test/recaptchaverifier/recaptchaverifier_test.js +++ /dev/null @@ -1,2152 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for recaptchaverifier.js. - */ - -goog.provide('fireauth.RecaptchaVerifierTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.BaseRecaptchaVerifier'); -goog.require('fireauth.GRecaptchaMockFactory'); -goog.require('fireauth.RecaptchaRealLoader'); -goog.require('fireauth.RecaptchaVerifier'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.Uri'); -goog.require('goog.dom'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.TestCase'); -goog.require('goog.testing.events'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.mockmatchers'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.RecaptchaVerifierTest'); - - -var mockControl; -var stubs = new goog.testing.PropertyReplacer(); -var app; -var grecaptcha; -var myElement, myElement2; -var ignoreArgument; -var loaderInstance; -var clock; -var grecaptchaMock; -var randomCounter; -var startInstanceId = fireauth.GRecaptchaMockFactory.START_INSTANCE_ID; -var expirationTimeMs = fireauth.GRecaptchaMockFactory.EXPIRATION_TIME_MS; -var solveTimeMs = fireauth.GRecaptchaMockFactory.SOLVE_TIME_MS; - - -/** - * Initialize reCAPTCHA mocks. This mocks the grecaptcha library. - */ -function initializeRecaptchaMocks() { - var recaptcha = { - // Recaptcha challenge ID. - 'challengesId': 0, - // Recaptcha array of instances. - 'instances': [], - // Render reCAPTCHA instance. - 'render': function(container, parameters) { - // New widget ID. - var id = recaptcha.instances.length; - // Element container. - var ele = goog.dom.getElement(container); - // Store new reCAPTCHA instance and its parameters. - recaptcha.instances.push({ - 'container': container, - 'response': 'response-' + recaptcha.challengesId, - 'userResponse': '', - 'parameters': parameters, - 'callback': parameters['callback'] || null, - 'expired-callback': parameters['expired-callback'] || null, - 'execute': false - }); - // Increment challenges ID. - recaptcha.challengesId++; - if (parameters.size !== 'invisible') { - // recaptcha can only be rendered on an empty element. - assertFalse(goog.dom.getChildren(ele).length > 0); - // Fill container with some HTML to simulate rendered widget. - ele.innerHTML = '
'; - } - // Return the reCAPTCHA widget ID. - return id; - }, - // Reset reCAPTCHA instance - 'reset': function(opt_id) { - // If widget ID not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - var parameters = recaptcha.instances[id]['parameters']; - // Reset instance challenge and its other properties. - recaptcha.instances[id] = { - 'container': parameters['container'], - 'response': 'response-' + recaptcha.challengesId, - 'userResponse': '', - 'parameters': parameters, - 'callback': parameters['callback'], - 'expired-callback': parameters['expired-callback'], - 'execute': false - }; - // Increment challenges ID. - recaptcha.challengesId++; - }, - // Returns reCAPTCHA instance's user response. - 'getResponse': function(opt_id) { - // If widget ID not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - // Return user response. - return recaptcha.instances[id]['userResponse'] || ''; - }, - // Executes the invisible reCAPTCHA. This will either force a reCAPTCHA - // visible challenge or resolve immediately. For testing, the former - // scenario is used. - 'execute': function(opt_id) { - // If widget id not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - var instance = recaptcha.instances[id]; - var parameters = instance['parameters']; - // execute should not be called on a visible reCAPTCHA. - if (parameters['size'] !== 'invisible') { - throw new Error('execute called on visible reCAPTCHA!'); - } - // Mark execute flag as true. - instance['execute'] = true; - }, - // For internal testing, simulates the reCAPTCHA corresponding to ID passed - // is solved. - 'solveResponse': function(opt_id) { - // If widget ID not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - var instance = recaptcha.instances[id]; - var parameters = instance['parameters']; - // Updated user response with the solve response. - instance['userResponse'] = instance['response']; - // execute must have been called on invisible reCAPTCHA. - if (!instance['execute'] && parameters['size'] === 'invisible') { - throw new Error('execute needs to be called before solving response!'); - } - // Trigger reCAPTCHA callback. - if (instance['callback'] && - typeof instance['callback'] == 'function') { - instance['callback'](instance['response']); - } - // Update next challenge response. - instance['response'] = 'response-' + recaptcha.challengesId; - recaptcha.challengesId++; - }, - // For internal testing, simulates the reCAPTCHA token corresponding to ID - // passed is expired. - 'expireResponse': function(opt_id) { - // If widget ID not provided, use last created one. - var id = typeof opt_id !== 'undefined' ? - opt_id : recaptcha.instances.length - 1; - // Assert instance exists. - assertNotNullNorUndefined(recaptcha.instances[id]); - var instance = recaptcha.instances[id]; - // Reset user response. - instance['userResponse'] = ''; - // Trigger expired callback. - if (instance['expired-callback'] && - typeof instance['expired-callback'] == 'function') { - instance['expired-callback'](); - } - // Reset execute. - instance['execute'] = false; - } - }; - // Fake the Recaptcha global object. - goog.global['grecaptcha'] = recaptcha; -} - - -/** - * Asserts the expected parameters used to initialize the reCAPTCHA. - * @param {number} widgetId The reCAPTCHA widget ID. - * @param {!Element|string} expectedContainer The expected reCAPTCHA container - * parameter. - * @param {!Object} expectedParams The expected parameters used to initialize - * the reCAPTCHA. - */ -function assertRecaptchaParams(widgetId, expectedContainer, expectedParams) { - // Confirm all expected parameters passed to the specified reCAPTCHA. - // This check excludes callbacks. - var instance = grecaptcha.instances[widgetId]; - var actualParameters = instance['parameters']; - for (var key in expectedParams) { - if (expectedParams.hasOwnProperty(key) && - key != 'callback' && - key != 'expired-callback') { - assertEquals(expectedParams[key], actualParameters[key]); - } - } - // Confirm the reCAPTCHA initialized on the expected container. - if (expectedParams.size !== 'invisible') { - // For visible reCAPTCHA, confirm expectedContainer element matches the - // parent of the actual container. - assertEquals( - goog.dom.getElement(expectedContainer), - goog.dom.getParentElement(instance.container)); - } else { - assertEquals(expectedContainer, instance.container); - } -} - - -function setUp() { - mockControl = new goog.testing.MockControl(); - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl.$resetAll(); - app = null; - // Create DIV test element and add to document. - myElement = goog.dom.createDom(goog.dom.TagName.DIV, {'id': 'recaptcha'}); - document.body.appendChild(myElement); - // Create another DIV test element and add to document. - myElement2 = goog.dom.createDom(goog.dom.TagName.DIV, {'id': 'recaptcha2'}); - document.body.appendChild(myElement2); - // Bypass singleton for tests so loaders are not shared among different tests. - loaderInstance = new fireauth.RecaptchaRealLoader(); - stubs.replace( - fireauth.RecaptchaRealLoader, - 'getInstance', - function() { - return loaderInstance; - }); - // Mock grecaptcha. - var randomCounter = 0; - var grecaptchaMock = new fireauth.GRecaptchaMockFactory(); - stubs.replace( - fireauth.GRecaptchaMockFactory, - 'getInstance', - function() { - return grecaptchaMock; - }); - stubs.replace( - fireauth.util, - 'generateRandomAlphaNumericString', - function(charCount) { - assertEquals(50, charCount); - return 'random' + (randomCounter++).toString(); - }); -} - - -function tearDown() { - // Destroy both elements. - if (myElement) { - goog.dom.removeNode(myElement); - myElement = null; - } - if (myElement2) { - goog.dom.removeNode(myElement2); - myElement2 = null; - } - // Reset global grecaptcha. - grecaptcha = null; - delete grecaptcha; - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } - delete goog.global['devCallback']; - delete goog.global['devExpiredCallback']; - stubs.reset(); -} - - -/** - * Sets the Auth service on the provided app instance if not already set. - * @param {!firebase.app.App} app The Firebase app instance on which to - * initialize the Auth service if not already available. - */ -function initializeAuthServiceOnApp(app) { - // Do nothing if auth() already exists. - if (typeof app.auth !== 'function') { - // Use set as Auth doesn't exist on the App instance. - stubs.set(app, 'auth', function() { - if (!this.auth_) { - this.auth_ = { - settings: { - // App verification enabled by default. - appVerificationDisabledForTesting: false - } - }; - } - return this.auth_; - }); - } -} - - -/** - * Simulates the current Auth language on the specified App instance. - * @param {!firebase.app.App} app The expected Firebase App instance. - * @param {?string} languageCode The default Auth language. - */ -function simulateAuthLanguage(app, languageCode) { - initializeAuthServiceOnApp(app); - app.auth().getLanguageCode = function() { - return languageCode; - }; -} - - -/** - * Simulates the current Auth frameworks on the specified App instance. - * @param {!firebase.app.App} app The expected Firebase app instance. - * @param {!Array} frameworks The current frameworks set on the Auth - * instance. - */ -function simulateAuthFramework(app, frameworks) { - initializeAuthServiceOnApp(app); - app.auth().getFramework = function() { - return frameworks; - }; -} - - -/** - * Install the test to run and runs it. - * @param {string} id The test identifier. - * @param {function():!goog.Promise} func The test function to run. - * @return {!goog.Promise} The result of the test. - */ -function installAndRunTest(id, func) { - /** - * @return {?goog.Promise} A promise that resolves on cleanup - * completion. - */ - var cleanupAppAndClock = function() { - var promises = []; - for (var i = 0; i < firebase.apps.length; i++) { - promises.push(firebase.apps[i].delete()); - } - var p = null; - if (promises.length) { - p = goog.Promise.all(promises).then(function() { - // Dispose clock then. Disposing before will throw an error in IE 11. - goog.dispose(clock); - }); - if (clock) { - // Some IE browsers like IE 11, native promise hangs if this is not - // called when clock is mocked. - // app.delete() will hang (it uses the native Promise). - clock.tick(); - } - } else if (clock) { - goog.dispose(clock); - } - return p; - }; - var testCase = new goog.testing.TestCase(); - testCase.addNewTest(id, func); - var error = null; - return testCase.runTestsReturningPromise().then(function(result) { - assertTrue(result.complete); - // Display error detected. - if (result.errors.length) { - fail(result.errors.join('\n')); - } - assertEquals(1, result.totalCount); - assertEquals(1, result.runCount); - assertEquals(1, result.successCount); - assertEquals(0, result.errors.length); - return cleanupAppAndClock(); - }).thenCatch(function(err) { - error = err; - return cleanupAppAndClock(); - }).then(function() { - if (error) { - throw error; - } - }); -} - - -function testBaseRecaptchaVerifier_noDOM() { - return installAndRunTest('testBaseAppVerifier_noHttpOrHttps', function() { - var isDOMSupported = mockControl.createMethodMock( - fireauth.util, 'isDOMSupported'); - isDOMSupported().$returns(false).$once(); - mockControl.$replayAll(); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED, - 'RecaptchaVerifier is only supported in a browser HTTP/HTTPS ' + - 'environment with DOM support.'); - var error = assertThrows(function() { - new fireauth.BaseRecaptchaVerifier('API_KEY', 'id'); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testBaseRecaptchaVerifier_noHttpOrHttps() { - return installAndRunTest('testBaseAppVerifier_noHttpOrHttps', function() { - var isHttpOrHttps = mockControl.createMethodMock( - fireauth.util, 'isHttpOrHttps'); - isHttpOrHttps().$returns(false).$once(); - mockControl.$replayAll(); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED, - 'RecaptchaVerifier is only supported in a browser HTTP/HTTPS ' + - 'environment.'); - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement); - return recaptchaVerifier.render().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); - }); -} - - -function testBaseRecaptchaVerifier_worker() { - return installAndRunTest('testBaseAppVerifier_noHttpOrHttps', function() { - // This gets called in some underlying dependencies at various points. - // It is not feasible counting the exact number of calls and the sequence - // they get called. It is better to use property replacer to stub this - // utility. - stubs.replace( - fireauth.util, - 'isWorker', - function() {return true;}); - var isHttpOrHttps = mockControl.createMethodMock( - fireauth.util, 'isHttpOrHttps'); - isHttpOrHttps().$returns(true).$once(); - mockControl.$replayAll(); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED, - 'RecaptchaVerifier is only supported in a browser HTTP/HTTPS ' + - 'environment.'); - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement); - return recaptchaVerifier.render().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); - }); -} - - -function testBaseRecaptchaVerifier_withSitekey() { - return installAndRunTest('testBaseAppVerifier_withSitekey', function() { - var options = { - sitekey: 'MY_SITE_KEY' - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'sitekey should not be provided for reCAPTCHA as one is ' + - 'automatically provisioned for the current project.'); - var error = assertThrows(function() { - new fireauth.BaseRecaptchaVerifier('API_KEY', myElement, options); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testBaseRecaptchaVerifier_visible_nonEmpty() { - return installAndRunTest('testBaseAppVerifier_visible_nonEmpty', function() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'reCAPTCHA container is either not found or already contains inner ' + - 'elements!'); - myElement.appendChild(goog.dom.createDom(goog.dom.TagName.DIV)); - var error = assertThrows(function() { - new fireauth.BaseRecaptchaVerifier('API_KEY', myElement); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testBaseRecaptchaVerifier_invisible_nonEmpty() { - return installAndRunTest( - 'testBaseAppVerifier_invisible_nonEmpty', function() { - myElement.appendChild(goog.dom.createDom(goog.dom.TagName.DIV)); - var returnValue = assertNotThrows(function() { - return new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, {'size': 'invisible'}); - }); - assertTrue(returnValue instanceof fireauth.BaseRecaptchaVerifier); - }); -} - - -function testBaseRecaptchaVerifier_invalidContainer() { - return installAndRunTest('testBaseAppVerifier_invalidContainer', function() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'reCAPTCHA container is either not found or already contains inner ' + - 'elements!'); - var error = assertThrows(function() { - new fireauth.BaseRecaptchaVerifier('API_KEY', 'invalidId'); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testBaseRecaptchaVerifier_validContainerId() { - return installAndRunTest('testBaseAppVerifier_validContainerId', function() { - app = firebase.initializeApp({ - apiKey: 'API_KEY' - }, 'test'); - var returnValue = assertNotThrows(function() { - return new fireauth.BaseRecaptchaVerifier( - 'API_KEY', 'recaptcha', {'size': 'compact'}); - }); - assertTrue(returnValue instanceof fireauth.BaseRecaptchaVerifier); - }); -} - - -function testBaseRecaptchaVerifier_render() { - return installAndRunTest('testBaseAppVerifier_render', function() { - // Confirm expected endpoint config and version passed to underlying RPC - // handler. - var version = '1.2.3'; - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', endpointConfig, version) - .$returns(rpcHandler); - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('', uri.getParameterValue('hl')); - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image' - }; - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, undefined, function() {return null;}, version, - endpointConfig); - assertEquals('recaptcha', recaptchaVerifier['type']); - // Confirm property is readonly. - recaptchaVerifier['type'] = 'modified'; - assertEquals('recaptcha', recaptchaVerifier['type']); - return recaptchaVerifier.render().then(function(widgetId) { - assertRecaptchaParams(widgetId, myElement, expectedParams); - assertEquals(0, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(0, widgetId); - grecaptcha.solveResponse(0); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Already rendered. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(0, widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - // Same unexpired response returned. - assertEquals('response-0', recaptchaToken); - // Expire response. - grecaptcha.expireResponse(0); - var resp = recaptchaVerifier.verify(); - // Solve response after expiration. New reCAPTCHA token should be - // returned. - grecaptcha.solveResponse(0); - return resp; - }).then(function(recaptchaToken) { - assertEquals('response-1', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_render_visible_testMode() { - return installAndRunTest('testBaseAppVerifier_visible_testMode', function() { - // Confirm expected endpoint config and version passed to underlying RPC - // handler. - var version = '1.2.3'; - var responseCallback = goog.testing.recordFunction(); - var expiredCallback = goog.testing.recordFunction(); - // Record calls to grecaptchaMock.render. - stubs.replace( - fireauth.GRecaptchaMockFactory.prototype, - 'render', - goog.testing.recordFunction( - fireauth.GRecaptchaMockFactory.prototype.render)); - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', endpointConfig, version) - .$returns(rpcHandler); - safeLoad(ignoreArgument).$never(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - // Install mock clock. - clock = new goog.testing.MockClock(true); - var params = { - 'size': 'compact', - 'theme': 'light', - 'type': 'image', - 'callback': responseCallback, - 'expired-callback': expiredCallback - }; - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, params, function() {return null;}, version, - endpointConfig, true /** Enable test mode */); - assertEquals('recaptcha', recaptchaVerifier['type']); - // Confirm property is readonly. - recaptchaVerifier['type'] = 'modified'; - assertEquals('recaptcha', recaptchaVerifier['type']); - return recaptchaVerifier.render().then(function(widgetId) { - // Mock instance should be rendered. - assertEquals( - 1, fireauth.GRecaptchaMockFactory.prototype.render.getCallCount()); - // For visible reCAPTCHA, confirm expectedContainer element matches the - // parent of the actual container. - assertEquals( - myElement, - goog.dom.getParentElement( - fireauth.GRecaptchaMockFactory.prototype.render.getLastCall() - .getArgument(0))); - var actualParams = fireauth.GRecaptchaMockFactory.prototype.render - .getLastCall().getArgument(1); - // Confirm expected parameters passed to reCAPTCHA. - assertEquals('SITE_KEY', actualParams['sitekey']); - assertEquals('light', actualParams['theme']); - assertEquals('image', actualParams['type']); - assertEquals('compact', actualParams['size']); - assertEquals(startInstanceId, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(startInstanceId, widgetId); - var verifyPromise = recaptchaVerifier.verify(); - assertEquals(0, responseCallback.getCallCount()); - clock.tick(solveTimeMs); - assertEquals(1, responseCallback.getCallCount()); - assertEquals('random0', responseCallback.getLastCall().getArgument(0)); - return verifyPromise; - }).then(function(recaptchaToken) { - assertEquals('random0', recaptchaToken); - // Already rendered. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(startInstanceId, widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - // Same unexpired response returned. - assertEquals('random0', recaptchaToken); - assertEquals(0, expiredCallback.getCallCount()); - // Expire response. - clock.tick(expirationTimeMs); - assertEquals(1, expiredCallback.getCallCount()); - var resp = recaptchaVerifier.verify(); - // Solve response after expiration. New reCAPTCHA token should be - // returned. - clock.tick(solveTimeMs); - return resp; - }).then(function(recaptchaToken) { - assertEquals(2, responseCallback.getCallCount()); - assertEquals('random1', responseCallback.getLastCall().getArgument(0)); - assertEquals('random1', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_render_invisible_testMode() { - return installAndRunTest('testBaseVerifier_invisible_testMode', function() { - // Confirm expected endpoint config and version passed to underlying RPC - // handler. - var version = '1.2.3'; - var responseCallback = goog.testing.recordFunction(); - var expiredCallback = goog.testing.recordFunction(); - // Record calls to grecaptchaMock.render. - stubs.replace( - fireauth.GRecaptchaMockFactory.prototype, - 'render', - goog.testing.recordFunction( - fireauth.GRecaptchaMockFactory.prototype.render)); - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', endpointConfig, version) - .$returns(rpcHandler); - safeLoad(ignoreArgument).$never(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - // Install mock clock. - clock = new goog.testing.MockClock(true); - var params = { - 'size': 'invisible', - 'theme': 'light', - 'type': 'image', - 'callback': responseCallback, - 'expired-callback': expiredCallback - }; - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, params, function() {return null;}, version, - endpointConfig, true /** Enable test mode */); - assertEquals('recaptcha', recaptchaVerifier['type']); - // Confirm property is readonly. - recaptchaVerifier['type'] = 'modified'; - assertEquals('recaptcha', recaptchaVerifier['type']); - return recaptchaVerifier.render().then(function(widgetId) { - // Confirm mock reCAPTCHA instance rendered. - assertEquals( - 1, fireauth.GRecaptchaMockFactory.prototype.render.getCallCount()); - // For invisible reCAPTCHA, confirm expectedContainer element matches the - // actual container. - assertEquals( - myElement, - fireauth.GRecaptchaMockFactory.prototype.render.getLastCall() - .getArgument(0)); - var actualParams = fireauth.GRecaptchaMockFactory.prototype.render - .getLastCall().getArgument(1); - // Confirm expected parameters passed to reCAPTCHA. - assertEquals('SITE_KEY', actualParams['sitekey']); - assertEquals('light', actualParams['theme']); - assertEquals('image', actualParams['type']); - assertEquals('invisible', actualParams['size']); - assertEquals(startInstanceId, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(startInstanceId, widgetId); - var verifyPromise = recaptchaVerifier.verify(); - // verify calls render underneath, wait for it to resolve before running - // clock. - return recaptchaVerifier.render().then(function() { - assertEquals(0, responseCallback.getCallCount()); - clock.tick(solveTimeMs); - assertEquals(1, responseCallback.getCallCount()); - assertEquals('random0', responseCallback.getLastCall().getArgument(0)); - return verifyPromise; - }); - }).then(function(recaptchaToken) { - assertEquals('random0', recaptchaToken); - // Already rendered. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(startInstanceId, widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - // Same unexpired response returned. - assertEquals('random0', recaptchaToken); - assertEquals(0, expiredCallback.getCallCount()); - // Expire response. - clock.tick(expirationTimeMs); - assertEquals(1, expiredCallback.getCallCount()); - // Element click should resolve with a token. - goog.testing.events.fireClickSequence(myElement); - clock.tick(solveTimeMs); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals(2, responseCallback.getCallCount()); - assertEquals('random1', responseCallback.getLastCall().getArgument(0)); - assertEquals('random1', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_render_offline() { - return installAndRunTest('testBaseAppVerifier_render_offline', function() { - // Install mock clock. - clock = new goog.testing.MockClock(true); - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var isOnline = mockControl.createMethodMock(fireauth.util, 'isOnline'); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Simulate first attempt fails due to network connection not being - // available. - isOnline().$does(function() { - goog.Promise.resolve().then(function() { - clock.tick(5000); - }); - return false; - }); - // Simulate first call does nothing due to network timeout. - safeLoad(ignoreArgument).$returns( - new goog.Promise(function(resolve, reject) {})); - // Simulate second attempt succeeding. - isOnline().$returns(true); - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('', uri.getParameterValue('hl')); - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image', - // Invalid callback names should be ignored. - 'callback': 'invalid', - 'expired-callback': 'invalid' - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement); - return recaptchaVerifier.render().thenCatch(function(error) { - // Initial attempt fails due to network connection. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Try again. It should be successful now. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertRecaptchaParams(widgetId, myElement, expectedParams); - assertEquals(0, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(0, widgetId); - grecaptcha.solveResponse(0); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Already rendered. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(0, widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - grecaptcha.expireResponse(0); - var resp = recaptchaVerifier.verify(); - grecaptcha.solveResponse(0); - return resp; - }).then(function(recaptchaToken) { - assertEquals('response-1', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_render_grecaptchaLoaded() { - return installAndRunTest('testBaseAppVerifier_recaptchaLoaded', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var expectedParams = { - 'sitekey': 'SITE_KEY' - }; - // In addition, test when the developer passes their own callbacks. - var devCallback = goog.testing.recordFunction(); - var devExpiredCallback = goog.testing.recordFunction(); - var params = { - 'callback': devCallback, - 'expired-callback': devExpiredCallback - }; - var resp = null; - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, params); - return recaptchaVerifier.render().then(function(widgetId) { - assertRecaptchaParams(widgetId, myElement, expectedParams); - assertEquals(0, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(0, widgetId); - // Simulate the reCAPTCHA challenge solved. - grecaptcha.solveResponse(0); - // Developer callback should be called with the expected token. - assertEquals(1, devCallback.getCallCount()); - assertEquals('response-0', devCallback.getLastCall().getArgument(0)); - // verify should resolve with the same token. - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Already rendered. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - // Cached response returned. - assertEquals(0, widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Expire the response. - grecaptcha.expireResponse(0); - // Developer expired callback should be triggered. - assertEquals(1, devExpiredCallback.getCallCount()); - // Try to verify again. - resp = recaptchaVerifier.verify(); - // Break thread to allow the verification to pick up the new response. - return goog.Promise.resolve(); - }).then(function() { - // Solve reCAPTCHA. Ths should be picked up. - grecaptcha.solveResponse(0); - // Developer token callback triggered with new token. - assertEquals(2, devCallback.getCallCount()); - assertEquals('response-1',devCallback.getLastCall().getArgument(0)); - assertEquals('response-1', grecaptcha.getResponse()); - return goog.Promise.resolve(); - }).then(function() { - // Expire token. - grecaptcha.expireResponse(0); - // Expired callback triggered. - assertEquals(2, devExpiredCallback.getCallCount()); - // Solve reCAPTCHA. - grecaptcha.solveResponse(0); - // Developer callback triggered with the new token. - assertEquals(3, devCallback.getCallCount()); - assertEquals('response-2', devCallback.getLastCall().getArgument(0)); - assertEquals('response-2', grecaptcha.getResponse()); - return goog.Promise.resolve(); - }).then(function() { - // Confirm the first resolved token picked up earlier. - return resp; - }).then(function(recaptchaToken) { - assertEquals('response-1', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_render_stringCallbacks() { - return installAndRunTest('testBaseAppVerifier_stringCallbacks', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var expectedParams = { - 'sitekey': 'SITE_KEY' - }; - // Test when the developer passes callback function names instead of the - // function references directly. - goog.global['devCallback'] = goog.testing.recordFunction(); - goog.global['devExpiredCallback'] = goog.testing.recordFunction(); - var params = { - 'callback': 'devCallback', - 'expired-callback': 'devExpiredCallback' - }; - var resp = null; - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, params); - return recaptchaVerifier.render().then(function(widgetId) { - assertRecaptchaParams(widgetId, myElement, expectedParams); - assertEquals(0, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(0, widgetId); - // Simulate the reCAPTCHA challenge solved. - grecaptcha.solveResponse(0); - // Developer callback should be called with the expected token. - assertEquals(1, goog.global['devCallback'].getCallCount()); - assertEquals( - 'response-0', - goog.global['devCallback'].getLastCall().getArgument(0)); - // verify should resolve with the same token. - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Already rendered. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - // Cached response returned. - assertEquals(0, widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Expire the response. - grecaptcha.expireResponse(0); - // Developer expired callback should be triggered. - assertEquals(1, goog.global['devExpiredCallback'].getCallCount()); - // Try to verify again. - resp = recaptchaVerifier.verify(); - // Break thread to allow the verification to pick up the new response. - return goog.Promise.resolve(); - }).then(function() { - // Solve reCAPTCHA. Ths should be picked up. - grecaptcha.solveResponse(0); - // Developer token callback triggered with new token. - assertEquals(2, goog.global['devCallback'].getCallCount()); - assertEquals( - 'response-1', - goog.global['devCallback'].getLastCall().getArgument(0)); - assertEquals('response-1', grecaptcha.getResponse()); - return goog.Promise.resolve(); - }).then(function() { - // Expire token. - grecaptcha.expireResponse(0); - // Expired callback triggered. - assertEquals(2, goog.global['devExpiredCallback'].getCallCount()); - // Solve reCAPTCHA. - grecaptcha.solveResponse(0); - // Developer callback triggered with the new token. - assertEquals(3, goog.global['devCallback'].getCallCount()); - assertEquals( - 'response-2', - goog.global['devCallback'].getLastCall().getArgument(0)); - assertEquals('response-2', grecaptcha.getResponse()); - return goog.Promise.resolve(); - }).then(function() { - // Confirm the first resolved token picked up earlier. - return resp; - }).then(function(recaptchaToken) { - assertEquals('response-1', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_getRecaptchaParamError() { - return installAndRunTest( - 'testBaseAppVerifier__recaptchaParamError', function() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Something unexpected happened.'); - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('', uri.getParameterValue('hl')); - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - // Simulate first attempt fails for some unknown reason. - rpcHandler.getRecaptchaParam().$once().$does(function() { - return goog.Promise.reject(expectedError); - }); - // Allow second attempt to succeed. - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image' - }; - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement); - return recaptchaVerifier.render().thenCatch(function(error) { - // First attempt fails with the same expected underlying error. - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - // Try again. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - // Resolves with the expected widget ID. - assertRecaptchaParams(widgetId, myElement, expectedParams); - assertEquals(0, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - // Same cached response. All other behavior should be the same. - assertEquals(0, widgetId); - }); - }); -} - - -function testBaseRecaptchaVerifier_verify_newToken() { - return installAndRunTest('testBaseAppVerifier_verify_newToken', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image' - }; - var resp = null; - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement); - // Simulate challenge solved after calling verify. - setTimeout(function() { - grecaptcha.solveResponse(0); - }, 10); - // This should render the reCAPTCHA and then after challenge is solve, - // resolve with the expected reCAPTCHA token. - return recaptchaVerifier.verify().then(function(recaptchaToken) { - assertRecaptchaParams(0, myElement, expectedParams); - assertEquals('response-0', recaptchaToken); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Expire the token response. - grecaptcha.expireResponse(0); - // Verify again. - resp = recaptchaVerifier.verify(); - return goog.Promise.resolve(); - }).then(function() { - // New response should be picked up. - grecaptcha.solveResponse(0); - assertEquals('response-1', grecaptcha.getResponse()); - return goog.Promise.resolve(); - }).then(function() { - // Expire and then solve again. - grecaptcha.expireResponse(0); - grecaptcha.solveResponse(0); - assertEquals('response-2', grecaptcha.getResponse()); - return goog.Promise.resolve(); - }).then(function() { - // Verify should resolve with the first expected response. - return resp; - }).then(function(recaptchaToken) { - assertEquals('response-1', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_idElement() { - return installAndRunTest('testBaseAppVerifier_idElement', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image' - }; - // Pass the element ID instead of the element itself for the container - // argument. - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', 'recaptcha'); - setTimeout(function() { - grecaptcha.solveResponse(0); - }, 10); - return recaptchaVerifier.verify().then(function(recaptchaToken) { - // reCAPTCHA initialized with the expected parameters. - assertRecaptchaParams(0, 'recaptcha', expectedParams); - // verify resolves with the expected response. - assertEquals('response-0', recaptchaToken); - // Same response cached until expiration. - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_verify_reset() { - return installAndRunTest('testBaseAppVerifier_verify_reset', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement); - var widgetIdReturned = null; - return recaptchaVerifier.render().then(function(widgetId) { - assertEquals(0, widgetId); - widgetIdReturned = widgetId; - grecaptcha.solveResponse(widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // After reset, the cached response is forgotten and the new one is used. - assertEquals('response-0', grecaptcha.getResponse(widgetIdReturned)); - recaptchaVerifier.reset(); - assertEquals('', grecaptcha.getResponse(widgetIdReturned)); - setTimeout(function() { - // The new solved challenge will be used as the old response is reset. - grecaptcha.solveResponse(0); - }, 10); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - // Since reCAPTCHA is reset, the new response should be used. - assertEquals('response-2', recaptchaToken); - assertEquals('response-2', grecaptcha.getResponse(0)); - }); - }); -} - - -function testBaseRecaptchaVerifier_multipleVerifiers() { - return installAndRunTest('testBaseAppVerifier_multipleVerifiers', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var recaptchaConfig1 = { - 'recaptchaSiteKey': 'SITE_KEY1' - }; - var recaptchaConfig2 = { - 'recaptchaSiteKey': 'SITE_KEY2' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandler2 = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument) - .$returns(rpcHandler); - rpcHandlerConstructor('API_KEY', null, ignoreArgument) - .$returns(rpcHandler2); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig1); - rpcHandler2.getRecaptchaParam().$once().$returns(recaptchaConfig2); - mockControl.$replayAll(); - - var expectedParams1 = { - 'sitekey': 'SITE_KEY1', - 'theme': 'dark ', - 'type': 'audio', - 'size': 'compact' - }; - var params1 = { - 'theme': 'dark ', - 'type': 'audio', - 'size': 'compact' - }; - var expectedParams2 = { - 'sitekey': 'SITE_KEY2', - 'theme': 'light', - 'type': 'image', - 'tabindex': 1 - }; - var params2 = { - 'theme': 'light', - 'type': 'image', - 'tabindex': 1 - }; - // Initialize 2 reCAPTCHA verifiers. - var recaptchaVerifier1 = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, params1); - var recaptchaVerifier2 = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement2, params2); - // Render first reCAPTCHA. - var p1 = recaptchaVerifier1.render().then(function(widgetId) { - // Expected widget ID returned. - assertEquals(0, widgetId); - // reCAPTCHA initialized with the expected parameters. - assertRecaptchaParams(widgetId, myElement, expectedParams1); - // Solve the challenge for the first reCAPTCHA. - grecaptcha.solveResponse(widgetId); - // verify should be resolved with the expected response. - return recaptchaVerifier1.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - }); - // Render second reCAPTCHA. - var p2 = recaptchaVerifier2.render().then(function(widgetId) { - // Expected widget ID returned. - assertEquals(1, widgetId); - // reCAPTCHA initialized with the expected parameters. - assertRecaptchaParams(widgetId, myElement2, expectedParams2); - // Solve the challenge for the second reCAPTCHA. - grecaptcha.solveResponse(widgetId); - // verify should be resolved with the expected response. - return recaptchaVerifier2.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-1', recaptchaToken); - }); - return goog.Promise.all([p1, p2]); - }); -} - - -function testBaseRecaptchaVerifier_verify_invisibleRecaptcha() { - return installAndRunTest('testBaseAppVerifier_verify_invisible', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'size': 'invisible' - }; - var resp = null; - // Initialize invisible reCAPTCHA. - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, {'size': 'invisible'}); - // Simulate invisible reCAPTCHA solved after some delay. - setTimeout(function() { - grecaptcha.execute(0); - grecaptcha.solveResponse(0); - }, 10); - // This should render and resolve with the expected response. - return recaptchaVerifier.verify().then(function(recaptchaToken) { - assertRecaptchaParams(0, myElement, expectedParams); - assertEquals('response-0', recaptchaToken); - // Same cached response returned. - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Expire response. - grecaptcha.expireResponse(0); - // verify again. - resp = recaptchaVerifier.verify(); - return goog.Promise.resolve(); - }).then(function() { - // After this is solved, verify should resolve with it. - // Even though execute is not call here, it will not throw an error as - // verify calls it underneath. - grecaptcha.solveResponse(0); - assertEquals('response-1', grecaptcha.getResponse()); - return goog.Promise.resolve(); - }).then(function() { - // Expire and solve again. This should be ignored by verify. - grecaptcha.expireResponse(0); - grecaptcha.execute(0); - grecaptcha.solveResponse(0); - assertEquals('response-2', grecaptcha.getResponse()); - return goog.Promise.resolve(); - }).then(function() { - return resp; - }).then(function(recaptchaToken) { - // Expected first response returned. - assertEquals('response-1', recaptchaToken); - }); - }); -} - - -function testBaseRecaptchaVerifier_visible_clear() { - return installAndRunTest('testBaseAppVerifier_visible_clear', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'RecaptchaVerifier instance has been destroyed.'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement); - return recaptchaVerifier.render().then(function(widgetId) { - // After rendering a visible reCAPTCHA, confirm reCAPTCHA HTML content - // added to that container. - assertEquals( - 1, - goog.dom.getChildren(goog.dom.getElement(myElement)).length); - // Clear reCAPTCHA. - recaptchaVerifier.clear(); - // reCAPTCHA content should be cleared. - assertEquals( - 0, - goog.dom.getChildren(goog.dom.getElement(myElement)).length); - // Calling verify will throw the expected error. - var error = assertThrows(function() { - recaptchaVerifier.verify(); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); - }); -} - - -function testBaseRecaptchaVerifier_pendingPromises_clear() { - return installAndRunTest( - 'testBaseAppVerifier_pendingPromises_clear', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - mockControl.$replayAll(); - - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement); - var p = new goog.Promise(function(resolve, reject) { - // This will remain pending until it is cancelled. - recaptchaVerifier.verify().then(function(token) { - reject('reCAPTCHA verify pending promise should be cancelled!'); - }).thenCatch(function(error) { - assertFalse(recaptchaVerifier.hasPendingPromises()); - // Cancellation error should trigger. - assertEquals( - 'RecaptchaVerifier instance has been destroyed.', error.message); - resolve(); - }); - // recaptchaVerifier verify promise should be pending. - assertTrue(recaptchaVerifier.hasPendingPromises()); - }); - // Clear reCAPTCHA. This should cancel the above pending promise. - recaptchaVerifier.clear(); - return p; - }); -} - - -function testBaseRecaptchaVerifier_invisible_clear() { - return installAndRunTest('testBaseAppVerifier_invisible_clear', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'RecaptchaVerifier instance has been destroyed.'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - // Append 2 DIVs to the container. This is fine for invisible reCAPTCHAs. - myElement.appendChild(goog.dom.createDom(goog.dom.TagName.DIV)); - myElement.appendChild(goog.dom.createDom(goog.dom.TagName.DIV)); - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, {'size': 'invisible'}); - return recaptchaVerifier.render().then(function(widgetId) { - // After rendering, no new content added to the container. - assertEquals( - 2, - goog.dom.getChildren(goog.dom.getElement(myElement)).length); - // Clear reCAPTCHA. - recaptchaVerifier.clear(); - // No changes to container. - assertEquals( - 2, - goog.dom.getChildren(goog.dom.getElement(myElement)).length); - // Calling any reCAPTCHA verifier API will throw the expected error. - var error = assertThrows(function() { - recaptchaVerifier.render(); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); - }); -} - - -function testBaseRecaptchaVerifier_localization() { - return installAndRunTest('testBaseAppVerifier_localization', function() { - var currentLanguageCode = null; - var appLanguageCode = null; - var getLanguageCode = function() {return appLanguageCode;}; - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - // First instance. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Dependency loaded with language code 'fr'. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('fr', uri.getParameterValue('hl')); - currentLanguageCode = 'fr'; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Second instance. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Dependency loaded with language code 'de'. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('de', uri.getParameterValue('hl')); - currentLanguageCode = 'de'; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Third instance. As same language is used, no dependency reload. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Fourth instance. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Dependency loaded with null language code. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('', uri.getParameterValue('hl')); - currentLanguageCode = null; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Fifth instance. As existing reCAPTCHA instance available, no dependency - // reload occurs. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Sixth instance. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Dependency loaded with language code 'ru'. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('ru', uri.getParameterValue('hl')); - currentLanguageCode = 'ru'; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Seventh instance. As existing reCAPTCHA instance available, no dependency - // reload occurs. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - // Set Auth language code to 'fr'. - appLanguageCode = 'fr'; - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image' - }; - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, null, getLanguageCode); - var recaptchaVerifier2; - // First instance rendered with 'fr' language code. - return recaptchaVerifier.render().then(function(widgetId) { - assertEquals('fr', currentLanguageCode); - // Change Auth language code to 'de'. - appLanguageCode = 'de'; - // Clear existing reCAPTCHA verifier. - recaptchaVerifier.clear(); - // Render new instance which should use 'de' language code. - recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, null, getLanguageCode); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals('de', currentLanguageCode); - // Clear existing instance. - recaptchaVerifier.clear(); - // As same language is used, no dependency reload. - recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, null, getLanguageCode); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals('de', currentLanguageCode); - // Language reset should force reload of dependency. - appLanguageCode = null; - // Clear existing reCAPTCHA instance. - recaptchaVerifier.clear(); - // Render new instance. It should force a dependency reload with null - // language code. - recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, null, getLanguageCode); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertNull(currentLanguageCode); - // Simulate reCAPTCHA is not cleared and language is changed. - appLanguageCode = 'ru'; - // No dependency reload should occur as it could break existing instance. - recaptchaVerifier2 = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement2, null, getLanguageCode); - return recaptchaVerifier2.render(); - }).then(function(widgetId) { - assertNull(currentLanguageCode); - // Clear both. - recaptchaVerifier.clear(); - recaptchaVerifier2.clear(); - // This should reload dependency with last language code. - recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, null, getLanguageCode); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - // Last loaded language code. - assertEquals('ru', currentLanguageCode); - // Clear existing instance. - recaptchaVerifier.clear(); - // Assume developer rendered external instance. - grecaptcha.render(myElement, expectedParams); - // Changing language will not reload dependencies. - appLanguageCode = 'ar'; - recaptchaVerifier2 = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement2, null, getLanguageCode); - return recaptchaVerifier2.render(); - }).then(function(widgetId) { - // Previous loaded language code remains. - assertEquals('ru', currentLanguageCode); - recaptchaVerifier2.clear(); - }); - }); -} - - -function testBaseRecaptchaVerifier_localization_alreadyLoaded() { - return installAndRunTest( - 'testBaseAppVerifier_locale_alreadyLoaded', function() { - // Simulate grecaptcha loaded. - initializeRecaptchaMocks(); - // Initialize after simulated reCAPTCHA dependency is loaded to make sure - // internal counter knows about the existence of grecaptcha. - loaderInstance = new fireauth.RecaptchaRealLoader(); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var appLanguageCode = 'de'; - var getLanguageCode = function() {return appLanguageCode;}; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // No language code reload. - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - // Even though this is the first instance of reCAPTCHA verifier, we can't - // know for sure that there is no other external instance. We shouldn't - // reload the reCAPTCHA dependencies. - var recaptchaVerifier = new fireauth.BaseRecaptchaVerifier( - 'API_KEY', myElement, null, getLanguageCode); - return recaptchaVerifier.render().then(function(widgetId) { - recaptchaVerifier.clear(); - }); - }); -} - - -function testRecaptchaVerifier_noApp() { - return installAndRunTest('testAppVerifier_noApp', function() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.ARGUMENT_ERROR, - 'No firebase.app.App instance is currently initialized.'); - var error = assertThrows(function() { - new fireauth.RecaptchaVerifier(myElement); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testRecaptchaVerifier_noApiKey() { - return installAndRunTest('testAppVerifier_noApiKey', function() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_API_KEY); - app = firebase.initializeApp({}, 'test'); - var error = assertThrows(function() { - new fireauth.RecaptchaVerifier(myElement, undefined, app); - }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - }); -} - - -function testRecaptchaVerifier_defaultApp() { - return installAndRunTest('testAppVerifier_defaultApp', function() { - app = firebase.initializeApp({ - apiKey: 'API_KEY' - }, 'test'); - stubs.set(firebase, 'app', function() { - return app; - }); - var returnValue = assertNotThrows(function() { - // Do not pass the App instance explicitly. - return new fireauth.RecaptchaVerifier('recaptcha', {'size': 'compact'}); - }); - assertTrue(returnValue instanceof fireauth.RecaptchaVerifier); - // Should be a subclass of fireauth.BaseRecaptchaVerifier. - assertTrue(returnValue instanceof fireauth.BaseRecaptchaVerifier); - }); -} - - -function testRecaptchaVerifier_render() { - return installAndRunTest('testAppVerifier_render', function() { - // Confirm expected endpoint config passed to underlying RPC handler. - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - stubs.replace( - fireauth.constants, - 'getEndpointConfig', - function(opt_id) { - return endpointConfig; - }); - // Confirm expected client version with the expected frameworks. - var expectedClientFullVersion = fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION, - [fireauth.util.Framework.FIREBASEUI]); - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - // RpcHandler should be initialized with expected API key, endpoint config - // and client version. - rpcHandlerConstructor('API_KEY', endpointConfig, expectedClientFullVersion) - .$returns(rpcHandler); - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('', uri.getParameterValue('hl')); - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - app = firebase.initializeApp({ - apiKey: 'API_KEY' - }, 'test'); - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image' - }; - // Simulate FirebaseUI logging. - simulateAuthFramework(app, [fireauth.util.Framework.FIREBASEUI]); - var recaptchaVerifier = new fireauth.RecaptchaVerifier( - myElement, undefined, app); - assertEquals('recaptcha', recaptchaVerifier['type']); - // Confirm property is readonly. - recaptchaVerifier['type'] = 'modified'; - assertEquals('recaptcha', recaptchaVerifier['type']); - return recaptchaVerifier.render().then(function(widgetId) { - assertRecaptchaParams(widgetId, myElement, expectedParams); - assertEquals(0, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(0, widgetId); - grecaptcha.solveResponse(0); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - assertEquals('response-0', recaptchaToken); - // Already rendered. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(0, widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - // Same unexpired response returned. - assertEquals('response-0', recaptchaToken); - // Expire response. - grecaptcha.expireResponse(0); - var resp = recaptchaVerifier.verify(); - // Solve response after expiration. New reCAPTCHA token should be - // returned. - grecaptcha.solveResponse(0); - return resp; - }).then(function(recaptchaToken) { - assertEquals('response-1', recaptchaToken); - }); - }); -} - - -function testRecaptchaVerifier_render_testingMode() { - return installAndRunTest('testAppVerifier_render_testingMode', function() { - // Confirm expected endpoint config passed to underlying RPC handler. - var endpoint = fireauth.constants.Endpoint.STAGING; - var endpointConfig = { - 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, - 'secureTokenEndpoint': endpoint.secureTokenEndpoint - }; - var responseCallback = goog.testing.recordFunction(); - var expiredCallback = goog.testing.recordFunction(); - stubs.replace( - fireauth.constants, - 'getEndpointConfig', - function(opt_id) { - return endpointConfig; - }); - // Record calls to grecaptchaMock.render. - stubs.replace( - fireauth.GRecaptchaMockFactory.prototype, - 'render', - goog.testing.recordFunction( - fireauth.GRecaptchaMockFactory.prototype.render)); - // Confirm expected client version with the expected frameworks. - var expectedClientFullVersion = fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebase.SDK_VERSION, - [fireauth.util.Framework.FIREBASEUI]); - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - // RpcHandler should be initialized with expected API key, endpoint config - // and client version. - rpcHandlerConstructor('API_KEY', endpointConfig, expectedClientFullVersion) - .$returns(rpcHandler); - safeLoad(ignoreArgument).$never(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - // Install mock clock. - clock = new goog.testing.MockClock(true); - app = firebase.initializeApp({ - apiKey: 'API_KEY' - }, 'test'); - // Simulate FirebaseUI logging. - simulateAuthFramework(app, [fireauth.util.Framework.FIREBASEUI]); - // Disable app verification. - app.auth().settings.appVerificationDisabledForTesting = true; - var params = { - 'size': 'compact', - 'theme': 'light', - 'type': 'image', - 'callback': responseCallback, - 'expired-callback': expiredCallback - }; - var recaptchaVerifier = new fireauth.RecaptchaVerifier( - myElement, params, app); - assertEquals('recaptcha', recaptchaVerifier['type']); - // Confirm property is readonly. - recaptchaVerifier['type'] = 'modified'; - assertEquals('recaptcha', recaptchaVerifier['type']); - return recaptchaVerifier.render().then(function(widgetId) { - // Mock reCAPTCHA should be rendered. - assertEquals( - 1, fireauth.GRecaptchaMockFactory.prototype.render.getCallCount()); - // For visible reCAPTCHA, confirm expectedContainer element matches the - // parent of the actual container. - assertEquals( - myElement, - goog.dom.getParentElement( - fireauth.GRecaptchaMockFactory.prototype.render.getLastCall() - .getArgument(0))); - var actualParams = fireauth.GRecaptchaMockFactory.prototype.render - .getLastCall().getArgument(1); - // Confirm expected parameters passed to reCAPTCHA. - assertEquals('SITE_KEY', actualParams['sitekey']); - assertEquals('light', actualParams['theme']); - assertEquals('image', actualParams['type']); - assertEquals('compact', actualParams['size']); - assertEquals(startInstanceId, widgetId); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(startInstanceId, widgetId); - var verifyPromise = recaptchaVerifier.verify(); - assertEquals(0, responseCallback.getCallCount()); - clock.tick(solveTimeMs); - assertEquals(1, responseCallback.getCallCount()); - assertEquals('random0', responseCallback.getLastCall().getArgument(0)); - return verifyPromise; - }).then(function(recaptchaToken) { - assertEquals('random0', recaptchaToken); - // Already rendered. - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals(startInstanceId, widgetId); - return recaptchaVerifier.verify(); - }).then(function(recaptchaToken) { - // Same unexpired response returned. - assertEquals('random0', recaptchaToken); - assertEquals(0, expiredCallback.getCallCount()); - // Expire response. - clock.tick(expirationTimeMs); - assertEquals(1, expiredCallback.getCallCount()); - var resp = recaptchaVerifier.verify(); - // Solve response after expiration. New reCAPTCHA token should be - // returned. - clock.tick(solveTimeMs); - return resp; - }).then(function(recaptchaToken) { - assertEquals(2, responseCallback.getCallCount()); - assertEquals('random1', responseCallback.getLastCall().getArgument(0)); - assertEquals('random1', recaptchaToken); - }); - }); -} - - -function testRecaptchaVerifier_localization() { - return installAndRunTest('testAppVerifier_localization', function() { - var currentLanguageCode = null; - var safeLoad = mockControl.createMethodMock(goog.net.jsloader, 'safeLoad'); - var recaptchaConfig = { - 'recaptchaSiteKey': 'SITE_KEY' - }; - var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); - var rpcHandlerConstructor = mockControl.createConstructorMock( - fireauth, 'RpcHandler'); - // First instance. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Dependency loaded with language code 'fr'. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('fr', uri.getParameterValue('hl')); - currentLanguageCode = 'fr'; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Second instance. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Dependency loaded with language code 'de'. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('de', uri.getParameterValue('hl')); - currentLanguageCode = 'de'; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Third instance. As same language is used, no dependency reload. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Fourth instance. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Dependency loaded with null language code. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('', uri.getParameterValue('hl')); - currentLanguageCode = null; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Fifth instance. As existing reCAPTCHA instance available, no dependency - // reload occurs. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Sixth instance. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - // Dependency loaded with language code 'ru'. - safeLoad(ignoreArgument) - .$does(function(url) { - var uri = goog.Uri.parse(goog.html.TrustedResourceUrl.unwrap(url)); - var callback = uri.getParameterValue('onload'); - assertEquals('ru', uri.getParameterValue('hl')); - currentLanguageCode = 'ru'; - initializeRecaptchaMocks(); - goog.global[callback](); - }) - .$once(); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - // Seventh instance. As existing reCAPTCHA instance available, no dependency - // reload occurs. - rpcHandlerConstructor('API_KEY', null, ignoreArgument).$returns(rpcHandler); - rpcHandler.getRecaptchaParam().$once().$returns(recaptchaConfig); - mockControl.$replayAll(); - - app = firebase.initializeApp({ - apiKey: 'API_KEY' - }, 'test'); - // Set Auth language code to 'fr'. - simulateAuthLanguage(app, 'fr'); - var expectedParams = { - 'sitekey': 'SITE_KEY', - 'theme': 'light', - 'type': 'image' - }; - var recaptchaVerifier = new fireauth.RecaptchaVerifier( - myElement, undefined, app); - var recaptchaVerifier2; - // First instance rendered with 'fr' language code. - return recaptchaVerifier.render().then(function(widgetId) { - assertEquals('fr', currentLanguageCode); - // Change Auth language code to 'de'. - simulateAuthLanguage(app, 'de'); - // Clear existing reCAPTCHA verifier. - recaptchaVerifier.clear(); - // Render new instance which should use 'de' language code. - recaptchaVerifier = new fireauth.RecaptchaVerifier( - myElement, undefined, app); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals('de', currentLanguageCode); - // Clear existing instance. - recaptchaVerifier.clear(); - // As same language is used, no dependency reload. - recaptchaVerifier = new fireauth.RecaptchaVerifier( - myElement, undefined, app); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertEquals('de', currentLanguageCode); - // Language reset should force reload of dependency. - simulateAuthLanguage(app, null); - // Clear existing reCAPTCHA instance. - recaptchaVerifier.clear(); - // Render new instance. It should force a dependency reload with null - // language code. - recaptchaVerifier = new fireauth.RecaptchaVerifier( - myElement, undefined, app); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - assertNull(currentLanguageCode); - // Simulate reCAPTCHA is not cleared and language is changed. - simulateAuthLanguage(app, 'ru'); - // No dependency reload should occur as it could break existing instance. - recaptchaVerifier2 = new fireauth.RecaptchaVerifier( - myElement2, undefined, app); - return recaptchaVerifier2.render(); - }).then(function(widgetId) { - assertNull(currentLanguageCode); - // Clear both. - recaptchaVerifier.clear(); - recaptchaVerifier2.clear(); - // This should reload dependency with last language code. - recaptchaVerifier = new fireauth.RecaptchaVerifier( - myElement, undefined, app); - return recaptchaVerifier.render(); - }).then(function(widgetId) { - // Last loaded language code. - assertEquals('ru', currentLanguageCode); - // Clear existing instance. - recaptchaVerifier.clear(); - // Assume developer rendered external instance. - grecaptcha.render(myElement, expectedParams); - // Changing language will not reload dependencies. - simulateAuthLanguage(app, 'ar'); - recaptchaVerifier2 = new fireauth.RecaptchaVerifier( - myElement2, undefined, app); - return recaptchaVerifier2.render(); - }).then(function(widgetId) { - // Previous loaded language code remains. - assertEquals('ru', currentLanguageCode); - recaptchaVerifier2.clear(); - }); - }); -} diff --git a/packages/auth/test/rpchandler_test.js b/packages/auth/test/rpchandler_test.js deleted file mode 100644 index e48f2f27eb1..00000000000 --- a/packages/auth/test/rpchandler_test.js +++ /dev/null @@ -1,9982 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for rpchandler.js. - */ - -goog.provide('fireauth.RpcHandlerTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.AuthErrorWithCredential'); -goog.require('fireauth.AuthProvider'); -goog.require('fireauth.FacebookAuthProvider'); -goog.require('fireauth.GoogleAuthProvider'); -goog.require('fireauth.OAuthCredential'); -goog.require('fireauth.OAuthProvider'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.json'); -goog.require('goog.net.CorsXmlHttpFactory'); -goog.require('goog.net.EventType'); -goog.require('goog.net.FetchXmlHttpFactory'); -goog.require('goog.net.XhrIo'); -goog.require('goog.net.XhrLike'); -goog.require('goog.object'); -goog.require('goog.testing.AsyncTestCase'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.RpcHandlerTest'); - - -var ignoreArgument; -var gapi = gapi || {}; -var stubs = new goog.testing.PropertyReplacer(); -var rpcHandler = null; -var expectedResponse = { - 'resp1': 'val1', - 'resp2': 'val2' -}; -// STS token server response. -var expectedStsTokenResponse = { - 'access_token': 'accessToken', - 'refresh_token': 'refreshToken', - 'expires_in': '3600' -}; -// Token response with expiresIn. -var tokenResponseWithExpiresIn = { - 'idToken': 'accessToken', - 'refreshToken': 'refreshToken', - 'expiresIn': '3600' -}; -// New token response without expiresIn. -var tokenResponse = { - 'idToken': 'accessToken', - 'refreshToken': 'refreshToken' -}; -var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); -var CURRENT_URL = 'http://www.example.com:8080/foo.htm'; -var clock; -var mockControl; -var delay = 30000; -var identityPlatformEndpoint = - fireauth.constants.Endpoint.PRODUCTION.identityPlatformEndpoint; -var now = new Date(); -var pendingCredResponse; -var pendingCredResponseWithAdditionalInfo; - - -function setUp() { - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return true;}); - stubs.replace( - goog.net.XhrIo.prototype, - 'send', - goog.testing.recordFunction()); - stubs.replace( - goog.net.XhrIo.prototype, - 'listen', - goog.testing.recordFunction()); - stubs.replace( - goog.net.XhrIo.prototype, - 'listenOnce', - goog.testing.recordFunction()); - stubs.replace( - goog.net.XhrIo.prototype, - 'setTimeoutInterval', - goog.testing.recordFunction()); - stubs.replace(fireauth.util, 'getCurrentUrl', function() { - return CURRENT_URL; - }); - rpcHandler = new fireauth.RpcHandler('apiKey'); - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl = new goog.testing.MockControl(); - mockControl.$resetAll(); - pendingCredResponse = { - 'mfaInfo': { - 'mfaEnrollmentId': 'ENROLLMENT_UID1', - 'enrolledAt': now.toISOString(), - 'phoneInfo': '+16505551234' - }, - 'mfaPendingCredential': 'PENDING_CREDENTIAL' - }; - pendingCredResponseWithAdditionalInfo = - goog.object.clone(pendingCredResponse); - goog.object.extend(pendingCredResponseWithAdditionalInfo, { - // Credential returned. - 'providerId': 'google.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthIdToken': 'googleIdToken', - 'oauthExpireIn': 3600, - // Additional user info data. - 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + - '"name":{"givenName":"John","familyName":"Doe"}}' - }); -} - - -/** - * @param {string} url The URL to make a request to. - * @param {string} method The HTTP send method. - * @param {?ArrayBuffer|?ArrayBufferView|?Blob|?Document|?FormData|string} - * data The request content. - * @param {?Object} headers The request content headers. - * @param {number} timeout The request timeout. - * @param {?Object} response The response to return. - */ -function assertSendXhrAndRunCallback( - url, method, data, headers, timeout, response) { - stubs.replace( - fireauth.RpcHandler.prototype, - 'sendXhr_', - function(actualUrl, callback, actualMethod, actualData, actualHeaders, - actualTimeout) { - assertEquals(url, actualUrl); - assertEquals(method, actualMethod); - assertEquals(data, actualData); - assertObjectEquals(headers, actualHeaders); - assertEquals(timeout, actualTimeout); - callback(response); - }); -} - - -/** - * Asserts that server errors are handled correctly. - * @param {function() : !goog.Promise} methodToTest The method that we are - * testing, which returns a Promise that we expect to reject with an error. - * @param {!Object} errorMap A map from server errors to the - * errors we expect from the method under test. - * @param {string} url The expected URL to which a request is made. - * @param {!Object} body The expected body of the request. - */ -function assertServerErrorsAreHandled(methodToTest, errorMap, url, body) { - errorMap = goog.object.clone(errorMap); - - asyncTestCase.waitForSignals(goog.object.getKeys(errorMap).length); - var promise = goog.Promise.resolve(); - goog.object.forEach(errorMap, function(expectedError, serverErrorCode) { - promise = promise.then(function() { - assertSendXhrAndRunCallback( - url, - 'POST', - goog.json.serialize(body), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - { - 'error': { - 'message': serverErrorCode - } - }); - return methodToTest().thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(expectedError), error); - asyncTestCase.signal(); - }); - }); - }); -} - - -function tearDown() { - pendingCredResponse = null; - pendingCredResponseWithAdditionalInfo = null; - stubs.reset(); - rpcHandler = null; - fireauth.RpcHandler.loadGApi_ = null; - goog.dispose(clock); - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } - delete goog.global['self']; -} - - -function testGetApiKey() { - assertEquals('apiKey', rpcHandler.getApiKey()); -} - - -function testUpdateGetTenantId() { - assertNull(rpcHandler.getTenantId()); - rpcHandler.updateTenantId('123456789012'); - assertEquals('123456789012', rpcHandler.getTenantId()); - rpcHandler.updateTenantId(null); - assertNull(rpcHandler.getTenantId()); -} - - -function testRpcHandler_XMLHttpRequest_notSupported() { - stubs.replace( - fireauth.RpcHandler, - 'getXMLHttpRequest', - function() {return undefined;}); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'The XMLHttpRequest compatibility library was not found.'); - var error = assertThrows(function() { new fireauth.RpcHandler('apiKey'); }); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); -} - - -function testRpcHandler_XMLHttpRequest_worker() { - // Test worker environment that FetchXmlHttpFactory is used in initialization - // of goog.net.XhrIo. - // Install mock clock. - clock = new goog.testing.MockClock(true); - // Simulates global self in a worker environment. - goog.global['self'] = {}; - var xhrInstance = mockControl.createStrictMock(goog.net.XhrLike); - var createInstance = mockControl.createMethodMock( - goog.net.FetchXmlHttpFactory.prototype, 'createInstance'); - stubs.reset(); - // Simulate worker environment. - stubs.replace( - fireauth.util, - 'isWorker', - function() {return true;}); - // Simulate fetch, Request and Headers API supported. - stubs.replace( - fireauth.util, - 'isFetchSupported', - function() {return true;}); - // No XMLHttpRequest available. - stubs.replace( - fireauth.RpcHandler, - 'getXMLHttpRequest', - function() {return undefined;}); - // Confirm RPC handler calls XHR instance from FetchXmlHttpFactory XHR. - createInstance().$returns(xhrInstance); - xhrInstance.open(ignoreArgument, ignoreArgument, ignoreArgument).$once(); - xhrInstance.setRequestHeader(ignoreArgument, ignoreArgument).$once(); - xhrInstance.send(ignoreArgument).$once(); - xhrInstance.abort().$once(); - asyncTestCase.waitForSignals(1); - mockControl.$replayAll(); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Simulate RPC and then timeout. - rpcHandler.fetchProvidersForIdentifier('user@example.com') - .thenCatch(function(error) { - asyncTestCase.signal(); - }); - // Timeout XHR request. - clock.tick(delay * 2); -} - - -function testRpcHandler_XMLHttpRequest_worker_fetchNotSupported() { - // Test worker environment where fetch, Headers and Request are not supported. - // Simulates global self in a worker environment. - goog.global['self'] = {}; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.OPERATION_NOT_SUPPORTED, - 'fetch, Headers and Request native APIs or equivalent Polyfills ' + - 'must be available to support HTTP requests from a Worker environment.'); - stubs.reset(); - // Simulate worker environment. - stubs.replace( - fireauth.util, - 'isWorker', - function() {return true;}); - // Simulate fetch, Request and Headers API not supported. - stubs.replace( - fireauth.util, - 'isFetchSupported', - function() {return false;}); - // No XMLHttpRequest available. - stubs.replace( - fireauth.RpcHandler, - 'getXMLHttpRequest', - function() {return undefined;}); - asyncTestCase.waitForSignals(1); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Simulate RPC and then expected error thrown. - rpcHandler.fetchProvidersForIdentifier('user@example.com') - .thenCatch(function(actualError) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, actualError); - asyncTestCase.signal(); - }); -} - - -function testRpcHandler_XMLHttpRequest_corsBrowser() { - // Test CORS browser environment that CorsXmlHttpFactory is used in - // initialization of goog.net.XhrIo. - // Install mock clock. - clock = new goog.testing.MockClock(true); - var xhrInstance = mockControl.createStrictMock(goog.net.XhrLike); - var createInstance = mockControl.createMethodMock( - goog.net.CorsXmlHttpFactory.prototype, 'createInstance'); - stubs.reset(); - // Non-worker environment. - stubs.replace( - fireauth.util, - 'isWorker', - function() {return false;}); - // CORS supporting browser. - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return true;}); - // Non-native environment. - stubs.replace( - fireauth.util, - 'isNativeEnvironment', - function() {return false;}); - // Confirm RPC handler calls XHR instance from CorsXmlHttpFactory XHR. - createInstance().$returns(xhrInstance); - xhrInstance.open(ignoreArgument, ignoreArgument, ignoreArgument).$once(); - xhrInstance.setRequestHeader(ignoreArgument, ignoreArgument).$once(); - xhrInstance.send(ignoreArgument).$once(); - xhrInstance.abort().$once(); - asyncTestCase.waitForSignals(1); - mockControl.$replayAll(); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Simulate RPC and then timeout. - rpcHandler.fetchProvidersForIdentifier('user@example.com') - .thenCatch(function(error) { - asyncTestCase.signal(); - }); - // Timeout XHR request. - clock.tick(delay * 2); -} - - -function testRpcHandler_XMLHttpRequest_reactNative() { - // Test react-native environment that built-in XMLHttpRequest is used in - // xhrFactory. - // Install mock clock. - clock = new goog.testing.MockClock(true); - var xhrInstance = mockControl.createStrictMock(goog.net.XhrLike); - var xhrConstructor = mockControl.createConstructorMock( - goog.net, 'XhrLike'); - stubs.reset(); - // CORS supporting environment. - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return true;}); - // Return native XMLHttpRequest.. - stubs.replace( - fireauth.RpcHandler, - 'getXMLHttpRequest', - function() {return xhrConstructor;}); - // React-native environment. - stubs.replace( - fireauth.util, - 'isNativeEnvironment', - function() {return true;}); - stubs.replace( - fireauth.util, - 'getEnvironment', - function() {return fireauth.util.Env.REACT_NATIVE;}); - // Confirm RPC handler calls XHR instance from factory XHR. - xhrConstructor().$returns(xhrInstance); - xhrInstance.open(ignoreArgument, ignoreArgument, ignoreArgument).$once(); - xhrInstance.setRequestHeader(ignoreArgument, ignoreArgument).$once(); - xhrInstance.send(ignoreArgument).$once(); - xhrInstance.abort().$once(); - asyncTestCase.waitForSignals(1); - mockControl.$replayAll(); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Simulate RPC and then timeout. - rpcHandler.fetchProvidersForIdentifier('user@example.com') - .thenCatch(function(error) { - asyncTestCase.signal(); - }); - // Timeout XHR request. - clock.tick(delay * 2); -} - - -function testRpcHandler_XMLHttpRequest_node() { - // Test node environment that Node.js implementation is used in xhrfactory. - // Install mock clock. - clock = new goog.testing.MockClock(true); - var xhrInstance = mockControl.createStrictMock(goog.net.XhrLike); - var xhrConstructor = mockControl.createConstructorMock( - goog.net, 'XhrLike'); - stubs.reset(); - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return true;}); - // Return mock XHR constructor. In a Node.js environment the polyfill library - // would be used. - stubs.replace( - fireauth.RpcHandler, - 'getXMLHttpRequest', - function() {return xhrConstructor;}); - // Node.js environment. - stubs.replace( - fireauth.util, - 'getEnvironment', - function() {return fireauth.util.Env.NODE;}); - // Confirm RPC handler calls XHR instance from factory XHR. - xhrConstructor().$returns(xhrInstance); - xhrInstance.open(ignoreArgument, ignoreArgument, ignoreArgument).$once(); - xhrInstance.setRequestHeader(ignoreArgument, ignoreArgument).$once(); - xhrInstance.send(ignoreArgument).$once(); - xhrInstance.abort().$once(); - asyncTestCase.waitForSignals(1); - mockControl.$replayAll(); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Simulate RPC and then timeout. - rpcHandler.fetchProvidersForIdentifier('user@example.com') - .thenCatch(function(error) { - asyncTestCase.signal(); - }); - // Timeout XHR request. - clock.tick(delay * 2); -} - - -/** - * Asserts and applies the goog.net.XhrIo send call. - * @param {string} url The expected XHR URL. - * @param {string} method The XHR expected HTTP method. - * @param {?ArrayBuffer|?ArrayBufferView|?Blob|?Document|?FormData|string=} data - * The expected request data. - * @param {?Object|undefined} headers The expected HTTP headers. - * @param {number} timeout The expected timeout. - * @param {?Object|undefined} resp The expected response to return. - */ -function assertXhrIoAndRunCallback(url, method, data, headers, timeout, resp) { - // Confirm correct parameters passed to goog.net.XhrIo.send. - assertEquals( - 1, - goog.net.XhrIo.prototype.send.getCallCount()); - assertEquals( - url, - goog.net.XhrIo.prototype.send.getLastCall().getArgument(0)); - assertEquals( - method, - goog.net.XhrIo.prototype.send.getLastCall().getArgument(1)); - assertEquals( - data, - goog.net.XhrIo.prototype.send.getLastCall().getArgument(2)); - assertObjectEquals( - headers, - goog.net.XhrIo.prototype.send.getLastCall().getArgument(3)); - assertEquals( - 1, - goog.net.XhrIo.prototype.setTimeoutInterval.getCallCount()); - assertEquals( - timeout, - goog.net.XhrIo.prototype.setTimeoutInterval.getLastCall().getArgument(0)); - // Get on complete callback. - var callback = goog.net.XhrIo.prototype.listen.getLastCall().getArgument(1); - // Returned expected response. - var self = { - // Return the response text. - getResponseText: function() { - return goog.json.serialize(resp); - } - }; - // Run on complete callback, pass self as this to return expected response. - callback.apply(self); -} - - -function testSendXhr_post() { - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Check response is passed to provided callback. - var responseRecorded = null; - var func = function(response) { - responseRecorded = response; - }; - var data = 'key1=value1&key2=value2'; - var headers = { - 'Content-Type': 'application/json' - }; - // Send XHR with test parameters. - rpcHandler.sendXhr_( - 'url1', - func, - 'POST', - data, - headers, - 5000); - // Confirm correct parameters passed and run on complete. - assertXhrIoAndRunCallback( - 'url1', - 'POST', - data, - headers, - 5000, - expectedResponse); - // Confirm callback called with expected response. - assertObjectEquals(expectedResponse, responseRecorded); -} - - -/** - * Tests client version being correctly sent with requests to Firebase Auth - * server. - */ -function testSendFirebaseBackendRequest_clientVersion() { - var clientVersion = 'Chrome/JsCore/3.0.0'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - // Pass client version in constructor. - var rpcHandler = new fireauth.RpcHandler( - 'apiKey', null, clientVersion); - var expectedDomains = [ - 'domain.com', - 'www.mydomain.com' - ]; - var serverResponse = { - 'authorizedDomains': [ - 'domain.com', - 'www.mydomain.com' - ] - }; - // The client version should be passed to header. - var expectedHeaders = { - 'Content-Type': 'application/json', - 'X-Client-Version': clientVersion - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50', - 'GET', - undefined, - expectedHeaders, - delay, - serverResponse); - rpcHandler.getAuthorizedDomains().then(function(domains) { - assertArrayEquals(expectedDomains, domains); - asyncTestCase.signal(); - }); -} - - -function testSendFirebaseBackendRequest_timeout() { - // Test network timeout error for Firebase backend request. - var actualError; - // Allow xhrIo requests. - stubs.reset(); - // Simulate CORS support. - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return true;}); - // Expected timeout error. - var timeoutError = new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - // Install mock clock. - clock = new goog.testing.MockClock(true); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Send request for backend API. - rpcHandler.fetchProvidersForIdentifier('user@example.com') - .thenCatch(function(error) { - // Record error. - actualError = error; - }); - // Timeout XHR request. - clock.tick(delay * 2); - // Timeout error should have been returned. - fireauth.common.testHelper.assertErrorEquals(timeoutError, actualError); -} - - -function testSendFirebaseBackendRequest_offline_falseAlert() { - // Install mock clock. - clock = new goog.testing.MockClock(true); - var expectedResponse = [ - 'google.com', - 'myauthprovider.com' - ]; - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'authUri': 'https://accounts.google.com/o/oauth2/auth?foo=bar', - 'providerId': 'google.com', - 'allProviders': [ - 'google.com', - 'myauthprovider.com' - ], - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'MY_SESSION_ID' - }; - var identifier = 'MY_ID'; - stubs.reset(); - // Simulate browser supports CORS. - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return true;}); - // Simulate expected URL returned for current URL. - stubs.replace( - fireauth.util, - 'getCurrentUrl', - function() { - return CURRENT_URL; - }); - // Simulate false alert navigator.onLine. - stubs.replace( - fireauth.util, - 'isOnline', - function() {return false;}); - // Overwrite XHR IO send to simulate a 4999ms delay before the response. - stubs.replace( - goog.net.XhrIo.prototype, - 'send', - function(url, httpMethod, data, headers) { - assertEquals( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - url); - assertEquals('POST', httpMethod); - assertEquals(goog.json.serialize(request), data); - assertObjectEquals( - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, headers); - clock.tick(4999); - this.dispatchEvent(goog.net.EventType.COMPLETE); - }); - // Simulate expected response returned. - stubs.replace( - goog.net.XhrIo.prototype, - 'getResponseText', - function() { - return JSON.stringify(serverResponse); - }); - - asyncTestCase.waitForSignals(1); - var request = { - 'identifier': identifier, - 'continueUri': CURRENT_URL - }; - rpcHandler.fetchProvidersForIdentifier(identifier) - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testSendFirebaseBackendRequest_offline_slowResponse() { - // Install mock clock. - clock = new goog.testing.MockClock(true); - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'authUri': 'https://accounts.google.com/o/oauth2/auth?foo=bar', - 'providerId': 'google.com', - 'allProviders': [ - 'google.com', - 'myauthprovider.com' - ], - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'MY_SESSION_ID' - }; - var identifier = 'MY_ID'; - stubs.reset(); - // Simulate browser supports CORS. - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return true;}); - // Simulate expected URL returned for current URL. - stubs.replace( - fireauth.util, - 'getCurrentUrl', - function() { - return CURRENT_URL; - }); - // Simulate false alert navigator.onLine. - stubs.replace( - fireauth.util, - 'isOnline', - function() {return false;}); - // Overwrite XHR IO send to simulate a 5000mx delay before the response. - stubs.replace( - goog.net.XhrIo.prototype, - 'send', - function(url, httpMethod, data, headers) { - assertEquals( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - url); - assertEquals('POST', httpMethod); - assertEquals(goog.json.serialize(request), data); - assertObjectEquals( - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, headers); - clock.tick(5000); - this.dispatchEvent(goog.net.EventType.COMPLETE); - }); - // Simulate expected response returned. - stubs.replace( - goog.net.XhrIo.prototype, - 'getResponseText', - function() { - return JSON.stringify(serverResponse); - }); - - asyncTestCase.waitForSignals(1); - var request = { - 'identifier': identifier, - 'continueUri': CURRENT_URL - }; - // Expected timeout error even though the request was eventually returned. - var timeoutError = new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - rpcHandler.fetchProvidersForIdentifier(identifier) - .thenCatch(function(actualError) { - // Timeout error should have been returned. - fireauth.common.testHelper.assertErrorEquals(timeoutError, actualError); - asyncTestCase.signal(); - }); -} - - -function testSendFirebaseBackendRequest_offline() { - // Test network timeout error for offline Firebase backend request. - asyncTestCase.waitForSignals(1); - // Allow xhrIo requests. - stubs.reset(); - // Simulate app offline. - stubs.replace( - fireauth.util, - 'isOnline', - function() {return false;}); - // Install mock clock. - clock = new goog.testing.MockClock(true); - // Expected timeout error. - var timeoutError = new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Send request for backend API. - rpcHandler.fetchProvidersForIdentifier('user@example.com') - .thenCatch(function(error) { - // Timeout error event without any wait (no tick in mockclock). - fireauth.common.testHelper.assertErrorEquals(timeoutError, error); - asyncTestCase.signal(); - }); - // Simulate short timeout when navigator.onLine is false. - clock.tick(5000); -} - - -function testSendStsTokenBackendRequest_timeout() { - // Test network timeout error for STS token backend request. - var actualError; - // Allow xhrIo requests. - stubs.reset(); - // Simulate CORS support. - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return true;}); - // Expected timeout error. - var timeoutError = new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - // Install mock clock. - clock = new goog.testing.MockClock(true); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Send request for backend API. - rpcHandler.requestStsToken({ - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).thenCatch(function(error) { - // Record error. - actualError = error; - }); - // Timeout XHR request. - clock.tick(delay * 2); - // Timeout error should have been returned. - fireauth.common.testHelper.assertErrorEquals(timeoutError, actualError); -} - - -function testSendStsTokenBackendRequest_offline() { - // Test network timeout error for offline STS token backend request. - asyncTestCase.waitForSignals(1); - // Allow xhrIo requests. - stubs.reset(); - // Simulate app offline. - stubs.replace( - fireauth.util, - 'isOnline', - function() {return false;}); - // Install mock clock. - clock = new goog.testing.MockClock(true); - // Expected timeout error. - var timeoutError = new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - rpcHandler = new fireauth.RpcHandler('apiKey'); - // Send request for backend API. - rpcHandler.requestStsToken({ - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).thenCatch(function(error) { - // Timeout error event without any wait (no tick in mockclock). - fireauth.common.testHelper.assertErrorEquals(timeoutError, error); - asyncTestCase.signal(); - }); - // Simulate short timeout when navigator.onLine is false. - clock.tick(5000); -} - - -function testSendXhr_corsUnsupported() { - var expectedResponse = { - 'key1': 'value1', - 'key2': 'value2' - }; - var recordedToken = 'token'; - gapi.auth = gapi.auth || {}; - gapi.client = gapi.client || {}; - stubs.reset(); - // Simulate GApi loaded. - stubs.set( - gapi.auth, - 'getToken', - function() { - return recordedToken; - }); - stubs.set( - gapi.auth, - 'setToken', - function(token) { - recordedToken = token; - }); - stubs.set( - gapi.client, - 'request', - function(request) { - assertEquals('none', request['authType']); - assertEquals('url1', request['path']); - assertEquals('GET', request['method']); - assertEquals(data, request['body']); - assertObjectEquals(headers, request['headers']); - request['callback'](expectedResponse); - asyncTestCase.signal(); - }); - stubs.set( - gapi.client, - 'setApiKey', - function(apiKey) { - assertEquals('apiKey', apiKey); - asyncTestCase.signal(); - }); - // Simulate browser that does not support CORS. - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return false;}); - var func = function(response) { - assertObjectEquals(expectedResponse, response); - // Verify token updated. - assertEquals('token', gapi.auth.getToken()); - asyncTestCase.signal(); - }; - var data = 'key1=value1&key2=value2'; - var headers = { - 'Content-Type': 'application/json' - }; - asyncTestCase.waitForSignals(3); - // Simulate GApi dependencies loaded. - fireauth.RpcHandler.loadGApi_ = goog.Promise.resolve(); - rpcHandler.sendXhr_( - 'url1', - func, - 'GET', - data, - headers, - 5000); -} - - -function testSendXhr_corsUnsupported_error() { - var expectedResponse = { - 'error': { - 'message': fireauth.RpcHandler.ServerError.CORS_UNSUPPORTED - } - }; - // Simulate browser that does not support CORS. - stubs.replace( - fireauth.util, - 'supportsCors', - function() {return false;}); - var func = function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }; - var data = 'key1=value1&key2=value2'; - var headers = { - 'Content-Type': 'application/json' - }; - asyncTestCase.waitForSignals(1); - fireauth.RpcHandler.loadGApi_ = goog.Promise.reject(); - rpcHandler.sendXhr_( - 'url1', - func, - 'GET', - data, - headers, - 5000); -} - - -function testSendSecureTokenBackendRequest_clientVersion() { - var clientVersion = 'Chrome/JsCore/3.0.0'; - // The client version should be passed to header. - var expectedHeaders = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'X-Client-Version': clientVersion - }; - // Pass client version in constructor. - var rpcHandler = new fireauth.RpcHandler( - 'apiKey', null, clientVersion); - asyncTestCase.waitForSignals(1); - // Confirm correct parameters passed and run on complete. - assertSendXhrAndRunCallback( - 'https://securetoken.googleapis.com/v1/token?key=apiKey', - 'POST', - 'grant_type=authorization_code&code=idToken', - expectedHeaders, - delay, - expectedStsTokenResponse); - // Send STS token request, default config will be used. - rpcHandler.requestStsToken( - { - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).then(function(response) { - assertObjectEquals( - expectedStsTokenResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestStsToken_updateClientVersion() { - asyncTestCase.waitForSignals(1); - // Confirm correct parameters passed and run on complete. - assertSendXhrAndRunCallback( - 'https://securetoken.googleapis.com/v1/token?key=apiKey', - 'POST', - 'grant_type=authorization_code&code=idToken', - { - 'Content-Type': 'application/x-www-form-urlencoded', - 'X-Client-Version': 'Chrome/JsCore/3.0.0/FirebaseCore-web' - }, - delay, - expectedStsTokenResponse); - // Update client version. - rpcHandler.updateClientVersion('Chrome/JsCore/3.0.0/FirebaseCore-web'); - // Send STS token request. - rpcHandler.requestStsToken( - { - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).then(function(response) { - assertObjectEquals( - expectedStsTokenResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestStsToken_removeClientVersion() { - asyncTestCase.waitForSignals(1); - // Confirm correct parameters passed and run on complete. - assertSendXhrAndRunCallback( - 'https://securetoken.googleapis.com/v1/token?key=apiKey', - 'POST', - 'grant_type=authorization_code&code=idToken', - { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - delay, - expectedStsTokenResponse); - // Remove client version. - rpcHandler.updateClientVersion(null); - // Send STS token request. - rpcHandler.requestStsToken( - { - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).then(function(response) { - assertObjectEquals( - expectedStsTokenResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestStsToken_default() { - asyncTestCase.waitForSignals(1); - // Confirm correct parameters passed and run on complete. - assertSendXhrAndRunCallback( - 'https://securetoken.googleapis.com/v1/token?key=apiKey', - 'POST', - 'grant_type=authorization_code&code=idToken', - fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, - delay, - expectedStsTokenResponse); - // Send STS token request, default config will be used. - rpcHandler.requestStsToken( - { - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).then(function(response) { - assertObjectEquals( - expectedStsTokenResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestStsToken_custom() { - asyncTestCase.waitForSignals(1); - // Reinitialize RPC handler using custom config. - rpcHandler = new fireauth.RpcHandler( - 'apiKey', - { - 'secureTokenEndpoint': 'http://localhost/token', - 'secureTokenTimeout': new fireauth.util.Delay(5000, 5000), - 'secureTokenHeaders': {'Content-Type': 'application/json'} - }); - // Confirm correct parameters passed and run on complete. - assertSendXhrAndRunCallback( - 'http://localhost/token?key=apiKey', - 'POST', - 'grant_type=authorization_code&code=idToken', - { - 'Content-Type': 'application/json' - }, - 5000, - expectedStsTokenResponse); - // Send STS token request, custom config will be used. - rpcHandler.requestStsToken( - { - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).then(function(response) { - assertObjectEquals( - expectedStsTokenResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestStsToken_invalidRequest() { - asyncTestCase.waitForSignals(1); - // Reinitialize RPC handler. - rpcHandler = new fireauth.RpcHandler( - 'apiKey'); - // Send STS token request, no XHR, invalid request. - rpcHandler.requestStsToken( - { - 'invalid': 'authorization_code', - 'code': 'idToken' - }).then( - function(response) {}, - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -function testRequestStsToken_unknownServerResponse() { - var serverResponse = {'error': 'INTERNAL_SERVER_ERROR'}; - asyncTestCase.waitForSignals(1); - // Confirm correct parameters passed and run on complete. - assertSendXhrAndRunCallback( - 'https://securetoken.googleapis.com/v1/token?key=apiKey', - 'POST', - 'grant_type=authorization_code&code=idToken', - fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, - delay, - serverResponse); - // Send STS token request, default config will be used. - rpcHandler.requestStsToken( - { - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).then( - function(response) {}, - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, - goog.json.serialize(serverResponse)), - error); - asyncTestCase.signal(); - }); -} - - -function testRequestStsToken_specificErrorResponse() { - // Server error response when token is expired. - var serverResponse = { - "error": { - "code": 400, - "message": "TOKEN_EXPIRED", - "status": "INVALID_ARGUMENT" - } - }; - asyncTestCase.waitForSignals(1); - // Confirm correct parameters passed and run on complete. - assertSendXhrAndRunCallback( - 'https://securetoken.googleapis.com/v1/token?key=apiKey', - 'POST', - 'grant_type=authorization_code&code=idToken', - fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, - delay, - serverResponse); - // Send STS token request, default config will be used. - rpcHandler.requestStsToken( - { - 'grant_type': 'authorization_code', - 'code': 'idToken' - }).then( - function(response) {}, - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED), - error); - asyncTestCase.signal(); - }); -} - - -function testRequestStsToken_emulator() { - asyncTestCase.waitForSignals(1); - // Confirm correct parameters passed and run on complete. - assertSendXhrAndRunCallback( - 'http://emulator.test.domain:1234/securetoken.googleapis.com/' + - 'v1/token?key=apiKey', - 'POST', - 'grant_type=authorization_code&code=idToken', - fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, - delay, - expectedStsTokenResponse); - // Set an emulator config. - rpcHandler.updateEmulatorConfig( - { url: 'http://emulator.test.domain:1234' }); - // Send STS token request, emulator config will be used. - rpcHandler - .requestStsToken({ 'grant_type': 'authorization_code', 'code': 'idToken' }) - .then(function (response) { - assertObjectEquals(expectedStsTokenResponse, response); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_success() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.requestFirebaseEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_emulator() { - var expectedResponse = { 'status': 'success' }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'http://emulator.test.domain:1234/www.googleapis.com/identitytoolkit' + - '/v3/relyingparty/method1?key=apiKey', - 'POST', - goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - // Set an emulator config. - rpcHandler.updateEmulatorConfig( - { url: 'http://emulator.test.domain:1234' }); - - rpcHandler - .requestFirebaseEndpoint( - 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }) - .then(function (response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testRequestIdentityPlatformEndpoint_success() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.requestIdentityPlatformEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestIdentityPlatformEndpoint_emulator() { - var expectedResponse = { 'status': 'success' }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'http://emulator.test.domain:1234/identitytoolkit.googleapis.com' + - '/v2/method1?key=apiKey', - 'POST', - goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - // Set an emulator config. - rpcHandler.updateEmulatorConfig( - { url: 'http://emulator.test.domain:1234' }); - rpcHandler - .requestIdentityPlatformEndpoint( - 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }) - .then(function (response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_updateClientVersion() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json', - 'X-Client-Version': 'Chrome/JsCore/3.0.0/FirebaseCore-web' - }, - delay, - expectedResponse); - // Update client version. - rpcHandler.updateClientVersion('Chrome/JsCore/3.0.0/FirebaseCore-web'); - rpcHandler.requestFirebaseEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestIdentityPlatformEndpoint_updateClientVersion() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json', - 'X-Client-Version': 'Chrome/JsCore/3.0.0/FirebaseCore-web' - }, - delay, - expectedResponse); - // Update client version. - rpcHandler.updateClientVersion('Chrome/JsCore/3.0.0/FirebaseCore-web'); - rpcHandler.requestIdentityPlatformEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_removeClientVersion() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json' - }, - delay, - expectedResponse); - // Remove client version. - rpcHandler.updateClientVersion(null); - rpcHandler.requestFirebaseEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestIdentityPlatformEndpoint_removeClientVersion() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json' - }, - delay, - expectedResponse); - // Remove client version. - rpcHandler.updateClientVersion(null); - rpcHandler.requestIdentityPlatformEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_setCustomLocaleHeader_success() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'fr' - }, - delay, - expectedResponse); - // Set French as custom Firebase locale header. - rpcHandler.updateCustomLocaleHeader('fr'); - rpcHandler.requestFirebaseEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestIdentityPlatformEndpoint_setCustomLocaleHeader_success() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'fr' - }, - delay, - expectedResponse); - // Set French as custom Firebase locale header. - rpcHandler.updateCustomLocaleHeader('fr'); - rpcHandler.requestIdentityPlatformEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_updateCustomLocaleHeader_success() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'de' - }, - delay, - expectedResponse); - // Set French as custom Firebase locale header. - rpcHandler.updateCustomLocaleHeader('fr'); - // Change to German. - rpcHandler.updateCustomLocaleHeader('de'); - rpcHandler.requestFirebaseEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestIdPlatformEndpoint_updateCustomLocaleHeader_success() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'de' - }, - delay, - expectedResponse); - // Set French as custom Firebase locale header. - rpcHandler.updateCustomLocaleHeader('fr'); - // Change to German. - rpcHandler.updateCustomLocaleHeader('de'); - rpcHandler.requestIdentityPlatformEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_removeCustomLocaleHeader_success() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json' - }, - delay, - expectedResponse); - // Set French as custom Firebase locale header. - rpcHandler.updateCustomLocaleHeader('fr'); - // Remove custom locale header. - rpcHandler.updateCustomLocaleHeader(null); - rpcHandler.requestFirebaseEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestIdPlatformEndpoint_removeCustomLocaleHeader_success() { - var expectedResponse = { - 'status': 'success' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - { - 'Content-Type': 'application/json' - }, - delay, - expectedResponse); - // Set French as custom Firebase locale header. - rpcHandler.updateCustomLocaleHeader('fr'); - // Remove custom locale header. - rpcHandler.updateCustomLocaleHeader(null); - rpcHandler.requestIdentityPlatformEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_error() { - // Error case. - var errorResponse = { - 'error': { - 'message': 'ERROR_CODE' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - errorResponse); - rpcHandler.requestFirebaseEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then( - function(response) {}, - function(e) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError('internal-error', - goog.json.serialize(errorResponse)), - e); - asyncTestCase.signal(); - }); -} - - -function testRequestIdentityPlatformEndpoint_error() { - // Error case. - var errorResponse = { - 'error': { - 'message': 'ERROR_CODE' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - errorResponse); - rpcHandler.requestIdentityPlatformEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }).then( - function(response) {}, - function(e) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError('internal-error', - goog.json.serialize(errorResponse)), - e); - asyncTestCase.signal(); - }); -} - - -function testRequestFirebaseEndpoint_networkError() { - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({'key1': 'value1'}), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - null); - rpcHandler.requestFirebaseEndpoint('method1', 'POST', {'key1': 'value1'}) - .then(null, function(e) { - fireauth.common.testHelper.assertErrorEquals(new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED), e); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testRequestIdentityPlatformEndpoint_networkError() { - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({'key1': 'value1'}), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - null); - rpcHandler - .requestIdentityPlatformEndpoint('method1', 'POST', {'key1': 'value1'}) - .then(null, function(e) { - fireauth.common.testHelper.assertErrorEquals(new fireauth.AuthError( - fireauth.authenum.Error.NETWORK_REQUEST_FAILED), e); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testRequestFirebaseEndpoint_keyInvalid() { - // Error case. - var errorResponse = { - 'error': { - 'errors': [ - { - 'domain': 'usageLimits', - 'reason': 'keyInvalid', - 'message': 'Bad Request' - } - ], - 'code': 400, - 'message': 'Bad Request' - } - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - '{}', - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - errorResponse); - rpcHandler.requestFirebaseEndpoint('method1', 'POST', {}) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_API_KEY), - error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testRequestIdentityPlatformEndpoint_keyInvalid() { - // Error case. - var errorResponse = { - 'error': { - 'errors': [ - { - 'domain': 'usageLimits', - 'reason': 'keyInvalid', - 'message': 'Bad Request' - } - ], - 'code': 400, - 'message': 'Bad Request' - } - }; - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - '{}', - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - errorResponse); - rpcHandler.requestIdentityPlatformEndpoint('method1', 'POST', {}) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_API_KEY), - error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testRequestFirebaseEndpoint_notAuthorized() { - // Error case. - var errorResponse = { - 'error': { - 'errors': [ - { - 'domain': 'usageLimits', - 'reason': 'ipRefererBlocked', - 'message': 'There is a per-IP or per-Referer restriction ' + - 'configured on your API key and the request does not match ' + - 'these restrictions. Please use the Google Developers Console ' + - 'to update your API key configuration if request from this IP ' + - 'or referer should be allowed.', - 'extendedHelp': 'https://console.developers.google.com' - } - ], - 'code': 403, - 'message': 'There is a per-IP or per-Referer restriction configured on ' + - 'your API key and the request does not match these restrictions. ' + - 'Please use the Google Developers Console to update your API key ' + - 'configuration if request from this IP or referer should be allowed.' - } - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - '{}', - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - errorResponse); - rpcHandler.requestFirebaseEndpoint('method1', 'POST', {}) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.APP_NOT_AUTHORIZED), - error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testRequestIdentityPlatformEndpoint_notAuthorized() { - // Error case. - var errorResponse = { - 'error': { - 'errors': [ - { - 'domain': 'usageLimits', - 'reason': 'ipRefererBlocked', - 'message': 'There is a per-IP or per-Referer restriction ' + - 'configured on your API key and the request does not match ' + - 'these restrictions. Please use the Google Developers Console ' + - 'to update your API key configuration if request from this IP ' + - 'or referer should be allowed.', - 'extendedHelp': 'https://console.developers.google.com' - } - ], - 'code': 403, - 'message': 'There is a per-IP or per-Referer restriction configured on ' + - 'your API key and the request does not match these restrictions. ' + - 'Please use the Google Developers Console to update your API key ' + - 'configuration if request from this IP or referer should be allowed.' - } - }; - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - '{}', - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - errorResponse); - rpcHandler.requestIdentityPlatformEndpoint('method1', 'POST', {}) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.APP_NOT_AUTHORIZED), - error); - asyncTestCase.signal(); - }); - asyncTestCase.waitForSignals(1); -} - - -function testRequestFirebaseEndpoint_customError() { - // Error case. - var errorResponse = { - 'error': { - 'message': 'ERROR_CODE' - } - }; - var errorMap = { - 'ERROR_CODE': fireauth.authenum.Error.INVALID_PASSWORD - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + - '=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - errorResponse); - rpcHandler.requestFirebaseEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }, - errorMap).then( - function(response) {}, - function(e) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_PASSWORD), - e); - asyncTestCase.signal(); - }); -} - - -function testRequestIdentityPlatformEndpoint_customError() { - // Error case. - var errorResponse = { - 'error': { - 'message': 'ERROR_CODE' - } - }; - var errorMap = { - 'ERROR_CODE': fireauth.authenum.Error.INVALID_PASSWORD - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'method1?key=apiKey', - 'POST', - goog.json.serialize({ - 'key1': 'value1', - 'key2': 'value2' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - errorResponse); - rpcHandler.requestIdentityPlatformEndpoint( - 'method1', - 'POST', - { - 'key1': 'value1', - 'key2': 'value2' - }, - errorMap).then( - function(response) {}, - function(e) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_PASSWORD), - e); - asyncTestCase.signal(); - }); -} - - -function testGetAuthorizedDomains() { - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedResponse = [ - 'domain.com', - 'www.mydomain.com' - ]; - var serverResponse = { - 'authorizedDomains': [ - 'domain.com', - 'www.mydomain.com' - ] - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50', - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.getAuthorizedDomains() - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testGetRecaptchaParam_success() { - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedResponse = { - 'recaptchaSiteKey': 'RECAPTCHA_SITE_KEY' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getRecaptchaParam?key=apiKey&cb=50', - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.getRecaptchaParam() - .then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testGetRecaptchaParam_invalidResponse_missingSitekey() { - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - // If for some reason, sitekey is not returned. - var serverResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getRecaptchaParam?key=apiKey&cb=50', - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.getRecaptchaParam() - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testGetDynamicLinkDomain_success() { - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var serverResponse = { - 'projectId': '12345678', - 'authorizedDomains': [ - 'domain.com', - 'www.mydomain.com' - ], - 'dynamicLinksDomain': 'example.app.goog.gl' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&returnDynamicLink=true', - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.getDynamicLinkDomain() - .then(function(dynamicLinksDomain) { - assertEquals('example.app.goog.gl', dynamicLinksDomain); - asyncTestCase.signal(); - }); -} - - -function testGetDynamicLinkDomain_internalError() { - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR); - // This should not happen in reality but need to confirm that logic checks - // for presence of dynamic links domain. - var serverResponse = { - 'projectId': '12345678', - 'authorizedDomains': [ - 'domain.com', - 'www.mydomain.com' - ] - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&returnDynamicLink=true', - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.getDynamicLinkDomain() - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testGetDynamicLinkDomain_notActivated() { - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.DYNAMIC_LINK_NOT_ACTIVATED); - var serverResponse = { - 'error': { - 'errors': [ - { - 'domain': 'global', - 'reason': 'invalid', - 'message': 'DYNAMIC_LINK_NOT_ACTIVATED' - } - ], - 'code': 400, - 'message': 'DYNAMIC_LINK_NOT_ACTIVATED' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&returnDynamicLink=true', - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.getDynamicLinkDomain() - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIsIosBundleIdValid_success() { - var iosBundleId = 'com.example.app'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var serverResponse = { - 'projectId': '12345678', - 'authorizedDomains': [ - 'domain.com', - 'www.mydomain.com' - ] - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&iosBundleId=' + - encodeURIComponent(iosBundleId), - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.isIosBundleIdValid(iosBundleId) - .then(function() { - asyncTestCase.signal(); - }); -} - - -function testIsIosBundleIdValid_error() { - var iosBundleId = 'com.example.app'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_APP_ID); - var serverResponse = { - 'error': { - 'errors': [ - { - 'message': 'INVALID_APP_ID' - } - ], - 'code': 400, - 'message': 'INVALID_APP_ID' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&iosBundleId=' + - encodeURIComponent(iosBundleId), - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.isIosBundleIdValid(iosBundleId) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIsAndroidPackageNameValid_success_noSha1Cert() { - var androidPackageName = 'com.example.app'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var serverResponse = { - 'projectId': '12345678', - 'authorizedDomains': [ - 'domain.com', - 'www.mydomain.com' - ] - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&androidPackageName=' + - encodeURIComponent(androidPackageName), - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.isAndroidPackageNameValid(androidPackageName) - .then(function() { - asyncTestCase.signal(); - }); -} - - -function testIsAndroidPackageNameValid_error_noSha1Cert() { - var androidPackageName = 'com.example.app'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_APP_ID); - var serverResponse = { - 'error': { - 'errors': [ - { - 'message': 'INVALID_APP_ID' - } - ], - 'code': 400, - 'message': 'INVALID_APP_ID' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&androidPackageName=' + - encodeURIComponent(androidPackageName), - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.isAndroidPackageNameValid(androidPackageName) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIsAndroidPackageNameValid_success_sha1Cert() { - var androidPackageName = 'com.example.app'; - var sha1Cert = 'SHA_1_ANDROID_CERT'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var serverResponse = { - 'projectId': '12345678', - 'authorizedDomains': [ - 'domain.com', - 'www.mydomain.com' - ] - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&androidPackageName=' + - encodeURIComponent(androidPackageName) + - '&sha1Cert=' + encodeURIComponent(sha1Cert), - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.isAndroidPackageNameValid(androidPackageName, sha1Cert) - .then(function() { - asyncTestCase.signal(); - }); -} - - -function testIsAndroidPackageNameValid_error_sha1Cert() { - var androidPackageName = 'com.example.app'; - var sha1Cert = 'INVALID_SHA_1_ANDROID_CERT'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CERT_HASH); - var serverResponse = { - 'error': { - 'errors': [ - { - 'message': 'INVALID_CERT_HASH' - } - ], - 'code': 400, - 'message': 'INVALID_CERT_HASH' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&androidPackageName=' + - encodeURIComponent(androidPackageName) + - '&sha1Cert=' + encodeURIComponent(sha1Cert), - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.isAndroidPackageNameValid(androidPackageName, sha1Cert) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testIsOAuthClientIdValid_success() { - var clientId = '123456.apps.googleusercontent.com'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var serverResponse = { - 'projectId': '12345678', - 'authorizedDomains': [ - 'domain.com', - 'www.mydomain.com' - ] - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&clientId=' + - encodeURIComponent(clientId), - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.isOAuthClientIdValid(clientId) - .then(function() { - asyncTestCase.signal(); - }); -} - - -function testIsOAuthClientIdValid_error() { - var clientId = '123456.apps.googleusercontent.com'; - // Simulate clock. - clock = new goog.testing.MockClock(); - clock.install(); - clock.tick(50); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_OAUTH_CLIENT_ID); - var serverResponse = { - 'error': { - 'errors': [ - { - 'message': 'INVALID_OAUTH_CLIENT_ID' - } - ], - 'code': 400, - 'message': 'INVALID_OAUTH_CLIENT_ID' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'getProjectConfig?key=apiKey&cb=50&clientId=' + - encodeURIComponent(clientId), - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.isOAuthClientIdValid(clientId) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testFetchSignInMethodsForIdentifier() { - var expectedResponse = ['google.com', 'emailLink']; - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'allProviders': [ - 'google.com', - "password" - ], - 'signinMethods': [ - 'google.com', - 'emailLink' - ], - 'registered': true, - 'sessionId': 'AXT8iKR2x89y2o7zRnroApio_uo' - }; - var identifier = 'user@example.com'; - - asyncTestCase.waitForSignals(1); - var request = {'identifier': identifier, 'continueUri': CURRENT_URL}; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.fetchSignInMethodsForIdentifier(identifier) - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testFetchSignInMethodsForIdentifier_tenantId() { - var expectedResponse = ['google.com', 'emailLink']; - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'allProviders': [ - 'google.com', - "password" - ], - 'signinMethods': [ - 'google.com', - 'emailLink' - ], - 'registered': true, - 'sessionId': 'AXT8iKR2x89y2o7zRnroApio_uo' - }; - var identifier = 'user@example.com'; - - asyncTestCase.waitForSignals(1); - var request = { - 'identifier': identifier, - 'continueUri': CURRENT_URL, - 'tenantId': '123456789012' - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.fetchSignInMethodsForIdentifier(identifier) - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testFetchSignInMethodsForIdentifier_noSignInMethodsReturned() { - var expectedResponse = []; - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'registered': true, - 'sessionId': 'AXT8iKR2x89y2o7zRnroApio_uo' - }; - var identifier = 'user@example.com'; - - asyncTestCase.waitForSignals(1); - var request = {'identifier': identifier, 'continueUri': CURRENT_URL}; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.fetchSignInMethodsForIdentifier(identifier) - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testFetchSignInMethodsForIdentifier_nonHttpOrHttps() { - // Simulate non http or https current URL. - stubs.replace(fireauth.util, 'getCurrentUrl', function() { - return 'chrome-extension://234567890/index.html'; - }); - stubs.replace(fireauth.util, 'getCurrentScheme', function() { - return 'chrome-extension:'; - }); - var expectedResponse = ['google.com', 'emailLink']; - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'allProviders': [ - 'google.com', - 'password' - ], - 'signinMethods': [ - 'google.com', - 'emailLink' - ], - 'registered': true, - 'sessionId': 'AXT8iKR2x89y2o7zRnroApio_uo' - }; - var identifier = 'user@example.com'; - - asyncTestCase.waitForSignals(1); - var request = { - 'identifier': identifier, - // A fallback HTTP URL should be used. - 'continueUri': 'http://localhost' - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.fetchSignInMethodsForIdentifier(identifier) - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testFetchSignInMethodsForIdentifier_serverCaughtError() { - var identifier = 'user@example.com'; - var requestBody = {'identifier': identifier, 'continueUri': CURRENT_URL}; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/createAuthUri?key=apiKey'; - - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_IDENTIFIER] = - fireauth.authenum.Error.INVALID_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CONTINUE_URI] = - fireauth.authenum.Error.INTERNAL_ERROR; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.fetchSignInMethodsForIdentifier(identifier); - }, errorMap, expectedUrl, requestBody); -} - - -function testFetchProvidersForIdentifier() { - var expectedResponse = [ - 'google.com', - 'myauthprovider.com' - ]; - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'authUri': 'https://accounts.google.com/o/oauth2/auth?foo=bar', - 'providerId': 'google.com', - 'allProviders': [ - 'google.com', - 'myauthprovider.com' - ], - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'MY_SESSION_ID' - }; - var identifier = 'MY_ID'; - - asyncTestCase.waitForSignals(1); - var request = { - 'identifier': identifier, - 'continueUri': CURRENT_URL - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.fetchProvidersForIdentifier(identifier) - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testFetchProvidersForIdentifier_tenantId() { - var expectedResponse = [ - 'google.com', - 'myauthprovider.com' - ]; - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'authUri': 'https://accounts.google.com/o/oauth2/auth?foo=bar', - 'providerId': 'google.com', - 'allProviders': [ - 'google.com', - 'myauthprovider.com' - ], - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'MY_SESSION_ID' - }; - var identifier = 'MY_ID'; - - asyncTestCase.waitForSignals(1); - var request = { - 'identifier': identifier, - 'continueUri': CURRENT_URL, - 'tenantId': '123456789012' - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.fetchProvidersForIdentifier(identifier) - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testFetchProvidersForIdentifier_nonHttpOrHttps() { - // Simulate non http or https current URL. - stubs.replace(fireauth.util, 'getCurrentUrl', function() { - return 'chrome-extension://234567890/index.html'; - }); - stubs.replace( - fireauth.util, - 'getCurrentScheme', - function() { - return 'chrome-extension:'; - }); - var expectedResponse = [ - 'google.com', - 'myauthprovider.com' - ]; - var serverResponse = { - 'kind': 'identitytoolkit#CreateAuthUriResponse', - 'authUri': 'https://accounts.google.com/o/oauth2/auth?foo=bar', - 'providerId': 'google.com', - 'allProviders': [ - 'google.com', - 'myauthprovider.com' - ], - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'MY_SESSION_ID' - }; - var identifier = 'MY_ID'; - - asyncTestCase.waitForSignals(1); - var request = { - 'identifier': identifier, - // A fallback HTTP URL should be used. - 'continueUri': 'http://localhost' - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'createAuthUri?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.fetchProvidersForIdentifier(identifier) - .then(function(response) { - assertArrayEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testFetchProvidersForIdentifier_serverCaughtError() { - var identifier = 'MY_IDENTIFIER'; - var requestBody = { - 'identifier': identifier, - 'continueUri': CURRENT_URL - }; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/createAuthUri?key=apiKey'; - - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_IDENTIFIER] = - fireauth.authenum.Error.INVALID_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CONTINUE_URI] = - fireauth.authenum.Error.INTERNAL_ERROR; - - assertServerErrorsAreHandled(function() { - return rpcHandler.fetchProvidersForIdentifier(identifier); - }, errorMap, expectedUrl, requestBody); -} - - -function testGetAccountInfoByIdToken() { - var expectedResponse = { - 'users': [{ - 'localId': '14584746072031976743', - 'email': 'uid123@fake.com', - 'emailVerified': true, - 'displayName': 'John Doe', - 'providerUserInfo': [ - { - 'providerId': 'google.com', - 'displayName': 'John Doe', - 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', - 'federatedId': 'https://accounts.google.com/123456789' - }, - { - 'providerId': 'twitter.com', - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/def' + - 'ault_profile_3_normal.png', - 'federatedId': 'http://twitter.com/987654321' - } - ], - 'photoUrl': 'http://abs.twimg.com/sticky/photo.png', - 'passwordUpdatedAt': 0.0, - 'disabled': false - }] - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountI' + - 'nfo?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.getAccountInfoByIdToken('ID_TOKEN').then(function(response) { - assertObjectEquals( - expectedResponse, - response); - asyncTestCase.signal(); - }); -} - - -function testVerifyCustomToken_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCusto' + - 'mToken?key=apiKey', - 'POST', - goog.json.serialize({ - 'token': 'CUSTOM_TOKEN', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyCustomToken('CUSTOM_TOKEN').then( - function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testVerifyCustomToken_multiFactorRequired() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, null, pendingCredResponse); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'verifyCustomToken?key=apiKey', - 'POST', - goog.json.serialize({ - 'token': 'CUSTOM_TOKEN', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - pendingCredResponse); - rpcHandler.verifyCustomToken('CUSTOM_TOKEN') - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testVerifyCustomToken_success_tenantId() { - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCusto' + - 'mToken?key=apiKey', - 'POST', - goog.json.serialize({ - 'token': 'CUSTOM_TOKEN', - 'returnSecureToken': true, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.verifyCustomToken('CUSTOM_TOKEN').then( - function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testVerifyCustomToken_unsupportedTenantOperation() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/verifyCustomToken?key=apiKey'; - var requestBody = { - 'token': 'CUSTOM_TOKEN', - 'returnSecureToken': true, - 'tenantId': '123456789012' - }; - rpcHandler.updateTenantId('123456789012'); - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_TENANT_OPERATION] = - fireauth.authenum.Error.UNSUPPORTED_TENANT_OPERATION; - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyCustomToken('CUSTOM_TOKEN'); - }, errorMap, expectedUrl, requestBody); -} - - -function testVerifyCustomToken_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/verifyCustomToken?key=apiKey'; - var token = 'CUSTOM_TOKEN'; - var requestBody = { - 'token': token, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CUSTOM_TOKEN] = - fireauth.authenum.Error.INTERNAL_ERROR; - errorMap[fireauth.RpcHandler.ServerError.INVALID_CUSTOM_TOKEN] = - fireauth.authenum.Error.INVALID_CUSTOM_TOKEN; - errorMap[fireauth.RpcHandler.ServerError.CREDENTIAL_MISMATCH] = - fireauth.authenum.Error.CREDENTIAL_MISMATCH; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - errorMap[fireauth.RpcHandler.ServerError.TENANT_ID_MISMATCH] = - fireauth.authenum.Error.TENANT_ID_MISMATCH; - - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyCustomToken(token); - }, errorMap, expectedUrl, requestBody); -} - - -function testServerProvidedErrorMessage_knownErrorCode() { - // Test when server returns an error message with the details appended: - // INVALID_CUSTOM_TOKEN : [error detail here] - // The above error message should generate an Auth error with code - // client equivalent of INVALID_CUSTOM_TOKEN and the message: - // [error detail here] - asyncTestCase.waitForSignals(1); - // Expected client side error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_CUSTOM_TOKEN, - 'Some specific reason.'); - // Server response. - var serverResponse = { - 'error': { - 'errors': [ - { - 'domain': 'global', - 'reason': 'invalid', - 'message': 'INVALID_CUSTOM_TOKEN : Some specific reason.' - } - ], - 'code': 400, - 'message': 'INVALID_CUSTOM_TOKEN : Some specific reason.' - } - }; - // Simulate invalid custom token. - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCusto' + - 'mToken?key=apiKey', - 'POST', - goog.json.serialize({ - 'token': 'CUSTOM_TOKEN', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyCustomToken('CUSTOM_TOKEN').thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testServerProvidedErrorMessage_unknownErrorCode() { - // Test when server returns an error message with the details appended: - // UNKNOWN_CODE : [error detail here] - // The above error message should generate an Auth error with internal-error - // ccode and the message: - // [error detail here] - asyncTestCase.waitForSignals(1); - // Expected client side error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Something strange happened.'); - // Server response. - var serverResponse = { - 'error': { - 'errors': [ - { - 'domain': 'global', - 'reason': 'invalid', - 'message': 'WHAAAAAT?: Something strange happened.' - } - ], - 'code': 400, - 'message': 'WHAAAAAT?: Something strange happened.' - } - }; - // Simulate unknown backend error. - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCusto' + - 'mToken?key=apiKey', - 'POST', - goog.json.serialize({ - 'token': 'CUSTOM_TOKEN', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyCustomToken('CUSTOM_TOKEN').thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testServerProvidedErrorMessage_noErrorCode() { - // Test when server returns an unexpected error message with a colon in the - // message field that it does not treat the string after the colon as the - // detailed error message. Instead the whole response should be serialized. - var errorMessage = 'Error getting access token from FACEBOOK, response: OA' + - 'uth2TokenResponse{params: %7B%22error%22:%7B%22message%22:%22This+IP+' + - 'can\'t+make+requests+for+that+application.%22,%22type%22:%22OAuthExce' + - 'ption%22,%22code%22:5,%22fbtrace_id%22:%22AHHaoO5cS1K%22%7D%7D&error=' + - 'OAuthException&error_description=This+IP+can\'t+make+requests+for+tha' + - 't+application., httpMetadata: HttpMetadata{status=400, cachePolicy=NO' + - '_CACHE, cacheDuration=null, staleWhileRevalidate=null, filename=null,' + - 'lastModified=null, headers=HTTP/1.1 200 OK\r\n\r\n, cookieList=[]}}, ' + - 'OAuth2 redirect uri is: https://example12345.firebaseapp.com/__/auth/' + - 'handler'; - asyncTestCase.waitForSignals(1); - // Server response. - var serverResponse = { - 'error': { - 'errors': [ - { - 'domain': 'global', - 'reason': 'invalid', - 'message': errorMessage - } - ], - 'code': 400, - 'message': errorMessage - } - }; - // Expected client side error (should contain the serialized response). - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - fireauth.util.stringifyJSON(serverResponse)); - // Simulate unknown backend error. - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testUnexpectedApiaryError() { - // Test when an unexpected Apiary error is returned that serialized server - // response is used as the client facing error message. - asyncTestCase.waitForSignals(1); - // Server response. - var serverResponse = { - 'error': { - 'errors': [ - { - 'domain': 'usageLimits', - 'reason': 'keyExpired', - 'message': 'Bad Request' - } - ], - 'code': 400, - 'message': 'Bad Request' - } - }; - // Expected client side error. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - goog.json.serialize(serverResponse)); - // Simulate unexpected Apiary error. - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCusto' + - 'mToken?key=apiKey', - 'POST', - goog.json.serialize({ - 'token': 'CUSTOM_TOKEN', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyCustomToken('CUSTOM_TOKEN').thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testGetErrorCodeDetails() { - // No error message. - assertUndefined( - fireauth.RpcHandler.getErrorCodeDetails('OPERATION_NOT_ALLOWED')); - // Error messages with variation of spaces before and after colon. - assertEquals( - 'Provider Id is not enabled in configuration.', - fireauth.RpcHandler.getErrorCodeDetails('OPERATION_NOT_ALLOWED : Provi' + - 'der Id is not enabled in configuration.')); - assertEquals( - 'Provider Id is not enabled in configuration.', - fireauth.RpcHandler.getErrorCodeDetails('OPERATION_NOT_ALLOWED:Provide' + - 'r Id is not enabled in configuration.')); - // Error message that contains colons. - assertEquals( - 'blabla:bla:::bla: something:', - fireauth.RpcHandler.getErrorCodeDetails('OPERATION_NOT_ALLOWED: blabl' + - 'a:bla:::bla: something:')); - // Error message that contains new line. - assertEquals( - 'Provider Id\nis not enabled in configuration.', - fireauth.RpcHandler.getErrorCodeDetails('OPERATION_NOT_ALLOWED : Provi' + - 'der Id\nis not enabled in configuration.')); -} - - -function testVerifyCustomToken_unknownServerResponse() { - // Test when server returns unexpected response with no error message. - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCusto' + - 'mToken?key=apiKey', - 'POST', - goog.json.serialize({ - 'token': 'CUSTOM_TOKEN', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - {}); - rpcHandler.verifyCustomToken('CUSTOM_TOKEN').thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignIn_success() { - var expectedResponse = {'idToken': 'ID_TOKEN'}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSi' + - 'gnin?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'user@example.com', - 'oobCode': 'OTP_CODE', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.emailLinkSignIn('user@example.com', 'OTP_CODE') - .then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignIn_error_multiFactorRequired() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, null, pendingCredResponse); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'emailLinkSignin?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'user@example.com', - 'oobCode': 'OTP_CODE', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - pendingCredResponse); - rpcHandler.emailLinkSignIn('user@example.com', 'OTP_CODE') - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignIn_success_tenantId() { - var expectedResponse = {'idToken': 'ID_TOKEN'}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSi' + - 'gnin?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'user@example.com', - 'oobCode': 'OTP_CODE', - 'returnSecureToken': true, - 'tenantId': 'TENANT_ID' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('TENANT_ID'); - rpcHandler.emailLinkSignIn('user@example.com', 'OTP_CODE') - .then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignIn_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/emailLinkSignin?key=apiKey'; - var email = 'user@example.com'; - var oobCode = 'OTP_CODE'; - var requestBody = { - 'email': email, - 'oobCode': oobCode, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_EMAIL] = - fireauth.authenum.Error.INVALID_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.TOO_MANY_ATTEMPTS_TRY_LATER] = - fireauth.authenum.Error.TOO_MANY_ATTEMPTS_TRY_LATER; - errorMap[fireauth.RpcHandler.ServerError.USER_DISABLED] = - fireauth.authenum.Error.USER_DISABLED; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.emailLinkSignIn(email, oobCode); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests invalid server response emailLinkSignIn error. - */ -function testEmailLinkSignIn_unknownServerResponse() { - // Test when server returns unexpected response with no error message. - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSi' + - 'gnin?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'user@example.com', - 'oobCode': 'OTP_CODE', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - {}); - rpcHandler.emailLinkSignIn('user@example.com', 'OTP_CODE') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignIn_emptyActionCodeError() { - // Test when empty action code is passed in emailLinkSignIn request. - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.emailLinkSignIn('user@example.com', '').thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), error); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignIn_invalidEmailError() { - // Test when invalid email is passed in emailLinkSignIn request. - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.emailLinkSignIn('user.invalid', 'OTP_CODE') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_EMAIL), - error); - asyncTestCase.signal(); - }); -} - - -function testVerifyPassword_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassw' + - 'ord?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'uid123@fake.com', - 'password': 'mysupersecretpassword', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyPassword('uid123@fake.com', 'mysupersecretpassword') - .then(function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testVerifyPassword_error_multiFactorRequired() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, null, pendingCredResponse); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'verifyPassword?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'uid123@fake.com', - 'password': 'mysupersecretpassword', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - pendingCredResponse); - rpcHandler.verifyPassword('uid123@fake.com', 'mysupersecretpassword') - .then(fail) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -function testVerifyPassword_success_tenantId() { - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassw' + - 'ord?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'uid123@fake.com', - 'password': 'mysupersecretpassword', - 'returnSecureToken': true, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.verifyPassword('uid123@fake.com', 'mysupersecretpassword') - .then(function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testVerifyPassword_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/verifyPassword?key=apiKey'; - var email = 'uid123@fake.com'; - var password = 'mysupersecretpassword'; - var requestBody = { - 'email': email, - 'password': password, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_EMAIL] = - fireauth.authenum.Error.INVALID_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_PASSWORD] = - fireauth.authenum.Error.INVALID_PASSWORD; - errorMap[fireauth.RpcHandler.ServerError.TOO_MANY_ATTEMPTS_TRY_LATER] = - fireauth.authenum.Error.TOO_MANY_ATTEMPTS_TRY_LATER; - errorMap[fireauth.RpcHandler.ServerError.USER_DISABLED] = - fireauth.authenum.Error.USER_DISABLED; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyPassword(email, password); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests invalid server response verifyPassword error. - */ -function testVerifyPassword_unknownServerResponse() { - // Test when server returns unexpected response with no error message. - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassw' + - 'ord?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'uid123@fake.com', - 'password': 'mysupersecretpassword', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - {}); - rpcHandler.verifyPassword('uid123@fake.com', 'mysupersecretpassword') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPassword error. - */ -function testVerifyPassword_invalidPasswordError() { - // Test when request is invalid. - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPassword('uid123@fake.com', '') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_PASSWORD), - error); - asyncTestCase.signal(); - }); -} - - -function testVerifyPassword_invalidEmailError() { - // Test when invalid email is passed in verifyPassword request. - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.verifyPassword('uid123.invalid', 'mysupersecretpassword') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_EMAIL), - error); - asyncTestCase.signal(); - }); -} - - -function testCreateAccount_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'signupNewUser?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'uid123@fake.com', - 'password': 'mysupersecretpassword', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.createAccount('uid123@fake.com', 'mysupersecretpassword') - .then(function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testCreateAccount_success_tenantId() { - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'signupNewUser?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'uid123@fake.com', - 'password': 'mysupersecretpassword', - 'returnSecureToken': true, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.createAccount('uid123@fake.com', 'mysupersecretpassword') - .then(function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testCreateAccount_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/signupNewUser?key=apiKey'; - var email = 'uid123@fake.com'; - var password = 'mysupersecretpassword'; - var requestBody = { - 'email': email, - 'password': password, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.EMAIL_EXISTS] = - fireauth.authenum.Error.EMAIL_EXISTS; - errorMap[fireauth.RpcHandler.ServerError.PASSWORD_LOGIN_DISABLED] = - fireauth.authenum.Error.OPERATION_NOT_ALLOWED; - errorMap[fireauth.RpcHandler.ServerError.OPERATION_NOT_ALLOWED] = - fireauth.authenum.Error.OPERATION_NOT_ALLOWED; - errorMap[fireauth.RpcHandler.ServerError.WEAK_PASSWORD] = - fireauth.authenum.Error.WEAK_PASSWORD; - errorMap[fireauth.RpcHandler.ServerError.ADMIN_ONLY_OPERATION] = - fireauth.authenum.Error.ADMIN_ONLY_OPERATION; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.createAccount(email, password); - }, errorMap, expectedUrl, requestBody); -} - - -function testCreateAccount_unknownServerResponse() { - // Test when server returns unexpected response with no error message. - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'signupNewUser?key=apiKey', - 'POST', - goog.json.serialize({ - 'email': 'uid123@fake.com', - 'password': 'mysupersecretpassword', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - {}); - rpcHandler.createAccount('uid123@fake.com', 'mysupersecretpassword') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -function testCreateAccount_noPasswordError() { - asyncTestCase.waitForSignals(1); - rpcHandler.createAccount('uid123@fake.com', '') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.WEAK_PASSWORD), - error); - asyncTestCase.signal(); - }); -} - - -function testCreateAccount_invalidEmailError() { - // Test when invalid email is passed in setAccountInfo request. - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.createAccount('uid123.invalid', 'mysupersecretpassword') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_EMAIL), - error); - asyncTestCase.signal(); - }); -} - - -function testDeleteAccount_success() { - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'deleteAccount?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - '{}'); - rpcHandler.deleteAccount('ID_TOKEN') - .then(function() { - asyncTestCase.signal(); - }); -} - - -function testDeleteAccount_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/deleteAccount?key=apiKey'; - var requestBody = { - 'idToken': 'ID_TOKEN' - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.CREDENTIAL_TOO_OLD_LOGIN_AGAIN] = - fireauth.authenum.Error.CREDENTIAL_TOO_OLD_LOGIN_AGAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_ID_TOKEN] = - fireauth.authenum.Error.INVALID_AUTH; - errorMap[fireauth.RpcHandler.ServerError.USER_NOT_FOUND] = - fireauth.authenum.Error.TOKEN_EXPIRED; - errorMap[fireauth.RpcHandler.ServerError.TOKEN_EXPIRED] = - fireauth.authenum.Error.TOKEN_EXPIRED; - errorMap[fireauth.RpcHandler.ServerError.USER_DISABLED] = - fireauth.authenum.Error.USER_DISABLED; - errorMap[fireauth.RpcHandler.ServerError.ADMIN_ONLY_OPERATION] = - fireauth.authenum.Error.ADMIN_ONLY_OPERATION; - - assertServerErrorsAreHandled(function() { - return rpcHandler.deleteAccount('ID_TOKEN'); - }, errorMap, expectedUrl, requestBody); -} - - -function testDeleteAccount_invalidRequestError() { - asyncTestCase.waitForSignals(1); - rpcHandler.deleteAccount().thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -function testSignInAnonymously_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'signupNewUser?key=apiKey', - 'POST', - goog.json.serialize({ - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.signInAnonymously() - .then(function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testSignInAnonymously_success_tenandId() { - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'signupNewUser?key=apiKey', - 'POST', - goog.json.serialize({ - 'returnSecureToken': true, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.signInAnonymously() - .then(function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testSignInAnonymously_unsupportedTenantOperation() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/signupNewUser?key=apiKey'; - var requestBody = { - 'returnSecureToken': true, - 'tenantId': '123456789012' - }; - rpcHandler.updateTenantId('123456789012'); - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_TENANT_OPERATION] = - fireauth.authenum.Error.UNSUPPORTED_TENANT_OPERATION; - assertServerErrorsAreHandled(function() { - return rpcHandler.signInAnonymously(); - }, errorMap, expectedUrl, requestBody); -} - - -function testSignInAnonymously_unknownServerResponse() { - // Test when server returns unexpected response with no error message. - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'signupNewUser?key=apiKey', - 'POST', - goog.json.serialize({ - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - {}); - rpcHandler.signInAnonymously() - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests multi-factor required verifyAssertion RPC error. - */ -function testVerifyAssertion_error_multiFactorRequired() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - pendingCredResponseWithAdditionalInfo); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'verifyAssertion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - pendingCredResponseWithAdditionalInfo); - rpcHandler.verifyAssertion({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion RPC call. - */ -function testVerifyAssertion_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion RPC call with the tenant ID being passed in - * the request explicitly. - */ -function testVerifyAssertion_success_passTenantIdExplicitly() { - // Test that if the tenant ID is explicitly passed in the request, the tenant - // ID on the RPC handler will be ignored. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - // Tenant ID on RPC handler should be TENANT_ID2, which is overridden by - // TENANT_ID1 in the request. - 'tenantId': 'TENANT_ID1', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('TENANT_ID2'); - rpcHandler.verifyAssertion({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'tenantId': 'TENANT_ID1' - }).then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion RPC call with no tenant ID passed in the - * request. - */ -function testVerifyAssertion_success_noTenantIdInRequest() { - // Test that if the tenant ID is not passed as part of verifyAssertionRequest - // explicitly, the tenant ID on RPC handler should be applied. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - 'returnSecureToken': true, - 'tenantId': 'TENANT_ID' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('TENANT_ID'); - rpcHandler.verifyAssertion({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion RPC call with nonce passed via sessionId. - */ -function testVerifyAssertion_withSessionIdNonce_success() { - var serverResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider' - }; - // Expected response should have nonce appended. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - 'nonce': 'NONCE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost/callback#id_token=ID_TOKEN&state=STATE', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost/callback#id_token=ID_TOKEN&state=STATE' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion RPC call with nonce passed via postBody. - */ -function testVerifyAssertion_withPostBodyNonce_success() { - var serverResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - }; - // Expected response should have nonce appended. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - 'nonce': 'NONCE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion RPC call with pendingToken in response. - */ -function testVerifyAssertion_pendingTokenResponse_success() { - // Nonce should not be injected since pending token is present in response. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion RPC call with pendingToken in request. - */ -function testVerifyAssertion_pendingTokenRequest_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN2', - 'oauthExpireIn': 3600 - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'pendingToken': 'PENDING_TOKEN', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'pendingToken': 'PENDING_TOKEN', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertion RPC call with invalid/expired pendingToken in request. - */ -function testVerifyAssertion_pendingTokenRequest_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/verifyAssertion?key=apiKey'; - var requestBody = { - 'pendingToken': 'PENDING_TOKEN', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_IDP_RESPONSE] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_PENDING_TOKEN] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyAssertion(requestBody); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests verifyAssertion RPC call with no recovery errorMessage. - */ -function testVerifyAssertion_returnIdpCredential_noRecoveryError() { - // Simulate server response containing unrecoverable errorMessage. - var serverResponse = { - 'federatedId': 'FEDERATED_ID', - 'providerId': 'google.com', - 'email': 'user@example.com', - 'emailVerified': true, - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE', - 'errorMessage': 'USER_DISABLED' - }; - // Expected error thrown. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertion RPC call with no sessionId passed. - */ -function testVerifyAssertion_error() { - asyncTestCase.waitForSignals(1); - rpcHandler.verifyAssertion({ - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion for linking RPC call. - */ -function testVerifyAssertionForLinking_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'existingIdToken', - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertionForLinking({ - 'idToken': 'existingIdToken', - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).then( - function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion for linking RPC call with nonce passed in - * sessionId field. - */ -function testVerifyAssertionForLinking_withSessionIdNonce_success() { - var serverResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider' - }; - // Expected response should have nonce appended. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - 'nonce': 'NONCE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'existingIdToken', - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost/callback#id_token=ID_TOKEN&state=STATE', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'idToken': 'existingIdToken', - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost/callback#id_token=ID_TOKEN&state=STATE' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion for linking RPC call with nonce passed in - * postBody field. - */ -function testVerifyAssertionForLinking_withPostBodyNonce_success() { - var serverResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - }; - // Expected response should have nonce appended. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - 'nonce': 'NONCE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'existingIdToken', - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'idToken': 'existingIdToken', - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion for linking RPC call with pending token - * returned in response. - */ -function testVerifyAssertionForLinking_pendingTokenResponse_success() { - // Nonce should not be injected since pending token is returned. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'existingIdToken', - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'idToken': 'existingIdToken', - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertion for linking RPC call with pending token - * in request. - */ -function testVerifyAssertionForLinking_pendingTokenRequest_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN2', - 'oauthExpireIn': 3600 - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'existingIdToken', - 'pendingToken': 'PENDING_TOKEN', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'idToken': 'existingIdToken', - 'pendingToken': 'PENDING_TOKEN', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertion for linking RPC call with no recovery errorMessage. - */ -function testVerifyAssertionForLinking_returnIdpCredential_noRecoveryError() { - // Simulate server response containing unrecoverable errorMessage. - var serverResponse = { - 'federatedId': 'FEDERATED_ID', - 'providerId': 'google.com', - 'email': 'user@example.com', - 'emailVerified': true, - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE', - 'errorMessage': 'USER_DISABLED' - }; - // Expected error thrown. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertionForLinking({ - 'idToken': 'ID_TOKEN', - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertion for linking RPC call with no idToken passed. - */ -function testVerifyAssertionForLinking_error() { - asyncTestCase.waitForSignals(1); - rpcHandler.verifyAssertionForLinking({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server caught verifyAssertion errors. - */ -function testVerifyAssertion_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/verifyAssertion?key=apiKey'; - var requestBody = { - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&' + - 'nonce=invalid', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_IDP_RESPONSE] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.USER_DISABLED] = - fireauth.authenum.Error.USER_DISABLED; - errorMap[fireauth.RpcHandler.ServerError.FEDERATED_USER_ID_ALREADY_LINKED] = - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE; - errorMap[fireauth.RpcHandler.ServerError.OPERATION_NOT_ALLOWED] = - fireauth.authenum.Error.OPERATION_NOT_ALLOWED; - errorMap[fireauth.RpcHandler.ServerError.USER_CANCELLED] = - fireauth.authenum.Error.USER_CANCELLED; - errorMap[fireauth.RpcHandler.ServerError.MISSING_OR_INVALID_NONCE] = - fireauth.authenum.Error.MISSING_OR_INVALID_NONCE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyAssertion(requestBody); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests invalid request verifyAssertionForIdToken error. - */ -function testVerifyAssertion_invalidRequestError() { - // Test when request is invalid. - asyncTestCase.waitForSignals(1); - rpcHandler.verifyAssertion({'postBody': '....'}).thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests need confirmation verifyAssertionForIdToken Auth linking error with - * OAuth response. - */ -function testVerifyAssertion_needConfirmationError_oauthResponseAndEmail() { - // Test Auth linking error when need confirmation flag is returned. - var credential = fireauth.GoogleAuthProvider.credential(null, - 'googleAccessToken'); - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential - }); - var expectedResponse = { - 'needConfirmation': true, - 'idToken': 'PENDING_TOKEN', - 'email': 'user@example.com', - 'oauthAccessToken': 'googleAccessToken', - 'providerId': 'google.com' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provide' + - 'r_id=google.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provider_id' + - '=google.com', - 'requestUri': 'http://localhost' - }).thenCatch( - function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests need confirmation verifyAssertionForIdToken Auth linking error with - * OAuth response and tenant ID. - */ -function testVerifyAssertion_needConfirmationError_tenantId() { - // Test Auth linking error when tenant ID is returned. - var credential = fireauth.GoogleAuthProvider.credential(null, - 'googleAccessToken'); - // Need confirmation error with tenant ID is returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential, - tenantId: 'TENANT_ID' - }); - var expectedResponse = { - 'needConfirmation': true, - 'idToken': 'PENDING_TOKEN', - 'email': 'user@example.com', - 'oauthAccessToken': 'googleAccessToken', - 'providerId': 'google.com', - 'tenantId': 'TENANT_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provide' + - 'r_id=google.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provider_id' + - '=google.com', - 'requestUri': 'http://localhost' - }).thenCatch( - function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests need confirmation verifyAssertionForIdToken Auth linking error with - * nonce passed via postBody. - */ -function testVerifyAssertion_needConfirmationError_nonceIdToken() { - // Expected error thrown with OIDC credential containing nonce. - var credential = new fireauth.OAuthProvider('oidc.provider').credential({ - 'idToken': 'OIDC_ID_TOKEN', - 'rawNonce': 'NONCE' - }); - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'needConfirmation': true, - 'email': 'user@example.com', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=' + - 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests need confirmation verifyAssertionForIdToken Auth linking error with - * nonce passed via sessionId. - */ -function testVerifyAssertion_needConfirmationError_idTokenSessionId() { - // Expected error thrown with OIDC credential containing nonce. - var credential = new fireauth.OAuthProvider('oidc.provider').credential({ - 'idToken': 'OIDC_ID_TOKEN', - 'rawNonce': 'NONCE' - }); - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'needConfirmation': true, - 'email': 'user@example.com', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider', - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider', - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests need confirmation verifyAssertionForIdToken Auth linking error with - * pendingToken in response. - */ -function testVerifyAssertion_needConfirmationError_pendingToken() { - // Expected error thrown with OIDC credential containing pending token and - // no nonce. - var credential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'OIDC_ID_TOKEN' - }, - 'oidc.provider'); - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'needConfirmation': true, - 'email': 'user@example.com', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider', - 'pendingToken': 'PENDING_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=' + - 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests need confirmation verifyAssertionForIdToken Auth linking error without - * OAuth response. - */ -function testVerifyAssertion_needConfirmationError_emailResponseOnly() { - // Test Auth linking error when need confirmation flag is returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.NEED_CONFIRMATION, - {email: 'user@example.com'}); - var expectedResponse = { - 'needConfirmation': true, - 'idToken': 'PENDING_TOKEN', - 'email': 'user@example.com' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provide' + - 'r_id=google.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provider_id' + - '=google.com', - 'requestUri': 'http://localhost' - }).thenCatch( - function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests need confirmation verifyAssertionForIdToken with no additional info in - * response. - */ -function testVerifyAssertion_needConfirmationError_noExtraInfo() { - // Test Auth error when need confirmation flag is returned but OAuth response - // missing. - var expectedResponse = { - 'needConfirmation': true - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provide' + - 'r_id=google.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provider_id' + - '=google.com', - 'requestUri': 'http://localhost' - }).thenCatch( - function(error) { - assertTrue(error instanceof fireauth.AuthError); - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.NEED_CONFIRMATION), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when FEDERATED_USER_ID_ALREADY_LINKED error - * is returned by the server. - */ -function testVerifyAssertion_credAlreadyInUseError_oauthResponseAndEmail() { - // Test Auth linking error when FEDERATED_USER_ID_ALREADY_LINKED errorMessage - // is returned. - var credential = fireauth.GoogleAuthProvider.credential(null, - 'googleAccessToken'); - // Credential already in use error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential - }); - var expectedResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'FEDERATED_USER_ID_ALREADY_LINKED', - 'email': 'user@example.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthExpireIn': 5183999, - 'providerId': 'google.com' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provide' + - 'r_id=google.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provider_id' + - '=google.com', - 'requestUri': 'http://localhost' - }).thenCatch( - function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when FEDERATED_USER_ID_ALREADY_LINKED error - * is returned by the server with tenant ID. - */ -function testVerifyAssertion_credAlreadyInUseError_tenantId() { - // Test Auth linking error when tenant ID is returned. - var credential = fireauth.GoogleAuthProvider.credential(null, - 'googleAccessToken'); - // Credential already in use error with tenant ID is returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential, - tenantId: 'TENANT_ID' - }); - var expectedResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'FEDERATED_USER_ID_ALREADY_LINKED', - 'email': 'user@example.com', - 'oauthAccessToken': 'googleAccessToken', - 'oauthExpireIn': 5183999, - 'providerId': 'google.com', - 'tenantId': 'TENANT_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provide' + - 'r_id=google.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provider_id' + - '=google.com', - 'requestUri': 'http://localhost' - }).thenCatch( - function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when FEDERATED_USER_ID_ALREADY_LINKED error - * is returned by the server with nonce passed via postBody. - */ -function testVerifyAssertion_credAlreadyInUseError_nonceIdToken() { - // Expected error thrown with OIDC credential containing nonce. - var credential = new fireauth.OAuthProvider('oidc.provider').credential({ - 'idToken': 'OIDC_ID_TOKEN', - 'rawNonce': 'NONCE' - }); - // Credential already in use error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'FEDERATED_USER_ID_ALREADY_LINKED', - 'email': 'user@example.com', - 'oauthExpireIn': 5183999, - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=' + - 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when FEDERATED_USER_ID_ALREADY_LINKED error - * is returned by the server with nonce passed via sessionId. - */ -function testVerifyAssertion_credAlreadyInUseError_idTokenSessionId() { - // Expected error thrown with OIDC credential containing nonce. - var credential = new fireauth.OAuthProvider('oidc.provider').credential({ - 'idToken': 'OIDC_ID_TOKEN', - 'rawNonce': 'NONCE' - }); - // Credential already in use error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'FEDERATED_USER_ID_ALREADY_LINKED', - 'email': 'user@example.com', - 'oauthExpireIn': 5183999, - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider', - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider', - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when FEDERATED_USER_ID_ALREADY_LINKED error - * is returned by the server with pending token in response. - */ -function testVerifyAssertion_credAlreadyInUseError_pendingToken() { - // Expected error thrown with OIDC credential containing pending token and no - // nonce. - var credential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'OIDC_ID_TOKEN' - }, - 'oidc.provider'); - // Credential already in use error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'FEDERATED_USER_ID_ALREADY_LINKED', - 'email': 'user@example.com', - 'oauthExpireIn': 5183999, - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider', - 'pendingToken': 'PENDING_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=' + - 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when EMAIL_EXISTS error - * is returned by the server. - */ -function testVerifyAssertion_emailExistsError_oauthResponseAndEmail() { - // Test Auth linking error when EMAIL_EXISTS errorMessage is returned. - var credential = fireauth.FacebookAuthProvider.credential( - 'facebookAccessToken'); - // Email exists error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.EMAIL_EXISTS, - { - email: 'user@example.com', - credential: credential - }); - var expectedResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'EMAIL_EXISTS', - 'email': 'user@example.com', - 'oauthAccessToken': 'facebookAccessToken', - 'oauthExpireIn': 5183999, - 'providerId': 'facebook.com' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'access_token=accessToken&provider_id=facebook.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'access_token=accessToken&provider_id=facebook.com', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when EMAIL_EXISTS error is returned by the - * server with tenant ID. - */ -function testVerifyAssertion_emailExistsError_tenantId() { - // Test Auth linking error when tenant ID is returned. - var credential = fireauth.FacebookAuthProvider.credential( - 'facebookAccessToken'); - // Email exists error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.EMAIL_EXISTS, - { - email: 'user@example.com', - credential: credential, - tenantId: 'TENANT_ID' - }); - var expectedResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'EMAIL_EXISTS', - 'email': 'user@example.com', - 'oauthAccessToken': 'facebookAccessToken', - 'oauthExpireIn': 5183999, - 'providerId': 'facebook.com', - 'tenantId': 'TENANT_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'access_token=accessToken&provider_id=facebook.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'access_token=accessToken&provider_id=facebook.com', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when EMAIL_EXISTS error - * is returned by the server with nonce in postBody. - */ -function testVerifyAssertion_emailExistsError_nonceIdToken() { - // Expected error thrown with OIDC credential containing nonce. - var credential = new fireauth.OAuthProvider('oidc.provider').credential({ - 'idToken': 'OIDC_ID_TOKEN', - 'rawNonce': 'NONCE' - }); - // Credential already in use error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.EMAIL_EXISTS, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'EMAIL_EXISTS', - 'email': 'user@example.com', - 'oauthExpireIn': 5183999, - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=' + - 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when EMAIL_EXISTS error - * is returned by the server with nonce in sessionId. - */ -function testVerifyAssertion_emailExistsError_idTokenSessionId() { - // Expected error thrown with OIDC credential containing nonce. - var credential = new fireauth.OAuthProvider('oidc.provider').credential({ - 'idToken': 'OIDC_ID_TOKEN', - 'rawNonce': 'NONCE' - }); - // Credential already in use error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.EMAIL_EXISTS, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'EMAIL_EXISTS', - 'email': 'user@example.com', - 'oauthExpireIn': 5183999, - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider', - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider', - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForIdToken when EMAIL_EXISTS error - * is returned by the server with pendingToken in response. - */ -function testVerifyAssertion_emailExistsError_pendingToken() { - // Expected error thrown with OIDC credential containing no nonce since - // pending token returned from server. - var credential = new fireauth.OAuthCredential( - 'oidc.provider', - { - 'pendingToken': 'PENDING_TOKEN', - 'idToken': 'OIDC_ID_TOKEN' - }, - 'oidc.provider'); - // Credential already in use error returned. - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.EMAIL_EXISTS, - { - email: 'user@example.com', - credential: credential - }); - var serverResponse = { - 'kind': 'identitytoolkit#VerifyAssertionResponse', - 'errorMessage': 'EMAIL_EXISTS', - 'email': 'user@example.com', - 'oauthExpireIn': 5183999, - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'providerId': 'oidc.provider', - 'pendingToken': 'PENDING_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=' + - 'NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertion({ - 'postBody': 'id_token=OIDC_ID_TOKEN&provider_id=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).thenCatch(function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertionForExisting RPC call. - */ -function testVerifyAssertionForExisting_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertionForExisting({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertionForExisting RPC call with the tenant ID - * being passed in the request explicitly. - */ -function testVerifyAssertionForExisting_success_passTenantIdExplicitly() { - // Test that if the tenant ID is explicitly passed in the request, the tenant - // ID on the RPC handler will be ignored. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - // Tenant ID on RPC handler should be TENANT_ID2, which is overridden by - // TENANT_ID1 in the request. - 'tenantId': 'TENANT_ID1', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - // Update the tenant ID on the RPC handler. - rpcHandler.updateTenantId('TENANT_ID2'); - rpcHandler.verifyAssertionForExisting({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'tenantId': 'TENANT_ID1' - }).then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertionForExisting RPC call with no tenant ID passed - * in the request. - */ -function testVerifyAssertionForExisting_success_noTenantIdInRequest() { - // Test that if the tenant ID is not passed as part of verifyAssertionRequest - // explicitly, the tenant ID on RPC handler should be applied. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true, - 'tenantId': 'TENANT_ID' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('TENANT_ID'); - rpcHandler.verifyAssertionForExisting({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests multi-factor required verifyAssertionForExisting RPC error. - */ -function testVerifyAssertionForExisting_error_multiFactorRequired() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.MFA_REQUIRED, - null, - pendingCredResponseWithAdditionalInfo); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'verifyAssertion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - pendingCredResponseWithAdditionalInfo); - rpcHandler.verifyAssertionForExisting({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).then(fail, function(error) { - fireauth.common.testHelper.assertErrorEquals( - expectedError, - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertionForExisting RPC call with nonce passed via - * sessionId. - */ -function testVerifyAssertionForExisting_withSessionIdNonce_success() { - var serverResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider' - }; - // Expected response should have nonce appended. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - 'nonce': 'NONCE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost/callback#id_token=ID_TOKEN&state=STATE', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertionForExisting({ - 'sessionId': 'NONCE', - 'requestUri': 'http://localhost/callback#id_token=ID_TOKEN&state=STATE' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertionForExisting RPC call with nonce passed via - * postBody. - */ -function testVerifyAssertionForExisting_withPostBodyNonce_success() { - var serverResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - }; - // Expected response should have nonce appended. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider', - 'nonce': 'NONCE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertionForExisting({ - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertionForExisting RPC call with pendingToken in - * response. - */ -function testVerifyAssertionForExisting_pendingTokenResponse_success() { - // Nonce should not be injected since pending token is present in response. - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN', - 'oauthExpireIn': 3600, - 'providerId': 'oidc.provider' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertionForExisting({ - 'postBody': 'id_token=ID_TOKEN&providerId=oidc.provider&nonce=NONCE', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyAssertionForExisting RPC call with pendingToken in - * request. - */ -function testVerifyAssertionForExisting_pendingTokenRequest_success() { - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'oauthIdToken': 'OIDC_ID_TOKEN', - 'pendingToken': 'PENDING_TOKEN2', - 'oauthExpireIn': 3600 - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'pendingToken': 'PENDING_TOKEN', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertionForExisting({ - 'pendingToken': 'PENDING_TOKEN', - 'requestUri': 'http://localhost' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertion for existing RPC call with no recovery errorMessage. - */ -function testVerifyAssertionForExisting_returnIdpCredential_noRecoveryError() { - // Simulate server response containing unrecoverable errorMessage. - var serverResponse = { - 'federatedId': 'FEDERATED_ID', - 'providerId': 'google.com', - 'email': 'user@example.com', - 'emailVerified': true, - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE', - 'errorMessage': 'USER_DISABLED' - }; - // Expected error thrown. - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.USER_DISABLED); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.verifyAssertionForExisting({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests verifyAssertionForExisting RPC call with no sessionId passed. - */ -function testVerifyAssertionForExisting_error() { - asyncTestCase.waitForSignals(1); - // Same client side validation as verifyAssertion. - rpcHandler.verifyAssertionForExisting({ - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests USER_NOT_FOUND verifyAssertionForExisting response. - */ -function testVerifyAssertionForExisting_error_userNotFound() { - // No user is found. No idToken returned. - var expectedResponse = { - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE', - 'errorMessage': 'USER_NOT_FOUND' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertionForExisting({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.USER_DELETED), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests no idToken verifyAssertionForExisting response. - */ -function testVerifyAssertionForExisting_error_userNotFound() { - // No idToken returned for whatever reason. - var expectedResponse = { - 'oauthAccessToken': 'ACCESS_TOKEN', - 'oauthExpireIn': 3600, - 'oauthAuthorizationCode': 'AUTHORIZATION_CODE' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAsse' + - 'rtion?key=apiKey', - 'POST', - goog.json.serialize({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyAssertionForExisting({ - 'sessionId': 'SESSION_ID', - 'requestUri': 'http://localhost/callback#oauthResponse' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyAssertionForExisting error. - */ -function testVerifyAssertionForExisting_invalidRequestError() { - // Test when request is invalid. - asyncTestCase.waitForSignals(1); - rpcHandler.verifyAssertionForExisting({'postBody': '....'}) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server caught verifyAssertionForExisting errors. - */ -function testVerifyAssertionForExisting_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/verifyAssertion?key=apiKey'; - var requestBody = { - 'postBody': 'id_token=googleIdToken&access_token=accessToken&provider_id=' + - 'google.com', - 'requestUri': 'http://localhost', - 'returnIdpCredential': true, - // autoCreate flag should be passed and set to false. - 'autoCreate': false, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_IDP_RESPONSE] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.USER_DISABLED] = - fireauth.authenum.Error.USER_DISABLED; - errorMap[fireauth.RpcHandler.ServerError.OPERATION_NOT_ALLOWED] = - fireauth.authenum.Error.OPERATION_NOT_ALLOWED; - errorMap[fireauth.RpcHandler.ServerError.USER_CANCELLED] = - fireauth.authenum.Error.USER_CANCELLED; - - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyAssertionForExisting(requestBody); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful sendSignInLinkToEmail RPC call with action code settings. - */ -function testSendSignInLinkToEmail_success_actionCodeSettings() { - var userEmail = 'user@example.com'; - var additionalRequestData = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }; - var expectedResponse = {'email': userEmail}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.EMAIL_SIGNIN, - 'email': userEmail, - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendSignInLinkToEmail('user@example.com', additionalRequestData) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendSignInLinkToEmail RPC call with custom locale. - */ -function testSendSignInLinkToEmail_success_customLocale() { - var userEmail = 'user@example.com'; - var additionalRequestData = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true - }; - var expectedResponse = {'email': userEmail}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.EMAIL_SIGNIN, - 'email': userEmail, - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true - }), - {'Content-Type': 'application/json', 'X-Firebase-Locale': 'es'}, - delay, - expectedResponse); - rpcHandler.updateCustomLocaleHeader('es'); - rpcHandler.sendSignInLinkToEmail('user@example.com', additionalRequestData) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendSignInLinkToEmail RPC call with tenant ID. - */ -function testSendSignInLinkToEmail_success_tenantId() { - var userEmail = 'user@example.com'; - var additionalRequestData = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'canHandleCodeInApp': true, - }; - var expectedResponse = {'email': userEmail}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.EMAIL_SIGNIN, - 'email': userEmail, - 'continueUrl': 'https://www.example.com/?state=abc', - 'canHandleCodeInApp': true, - 'tenantId': 'TENANT_ID' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('TENANT_ID'); - rpcHandler.sendSignInLinkToEmail('user@example.com', additionalRequestData) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid email sendSignInLinkToEmail error. - */ -function testSendSignInLinkToEmail_invalidEmailError() { - // Test when invalid email is passed in getOobCode request. - var additionalRequestData = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true - }; - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.sendSignInLinkToEmail('user.invalid', additionalRequestData) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_EMAIL), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response sendSignInLinkToEmail error. - */ -function testSendSignInLinkToEmail_unknownServerResponse() { - var userEmail = 'user@example.com'; - var additionalRequestData = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true - }; - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.EMAIL_SIGNIN, - 'email': userEmail, - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendSignInLinkToEmail(userEmail, additionalRequestData) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side sendSignInLinkToEmail error. - */ -function testSendSignInLinkToEmail_serverCaughtError() { - var userEmail = 'user@example.com'; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/getOobConfirmationCode?key=apiKey'; - var requestBody = { - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.EMAIL_SIGNIN, - 'email': userEmail - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_RECIPIENT_EMAIL] = - fireauth.authenum.Error.INVALID_RECIPIENT_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SENDER] = - fireauth.authenum.Error.INVALID_SENDER; - errorMap[fireauth.RpcHandler.ServerError.INVALID_MESSAGE_PAYLOAD] = - fireauth.authenum.Error.INVALID_MESSAGE_PAYLOAD; - - // Action code settings related errors. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CONTINUE_URI] = - fireauth.authenum.Error.INVALID_CONTINUE_URI; - errorMap[fireauth.RpcHandler.ServerError.MISSING_ANDROID_PACKAGE_NAME] = - fireauth.authenum.Error.MISSING_ANDROID_PACKAGE_NAME; - errorMap[fireauth.RpcHandler.ServerError.MISSING_IOS_BUNDLE_ID] = - fireauth.authenum.Error.MISSING_IOS_BUNDLE_ID; - errorMap[fireauth.RpcHandler.ServerError.UNAUTHORIZED_DOMAIN] = - fireauth.authenum.Error.UNAUTHORIZED_DOMAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_DYNAMIC_LINK_DOMAIN] = - fireauth.authenum.Error.INVALID_DYNAMIC_LINK_DOMAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.sendSignInLinkToEmail(userEmail, {}); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful sendPasswordResetEmail RPC call with action code settings. - */ -function testSendPasswordResetEmail_success_actionCodeSettings() { - var userEmail = 'user@example.com'; - var additionalRequestData = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.PASSWORD_RESET, - 'email': userEmail, - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendPasswordResetEmail('user@example.com', additionalRequestData) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendPasswordResetEmail RPC call with no action code - * settings. - */ -function testSendPasswordResetEmail_success_noActionCodeSettings() { - var userEmail = 'user@example.com'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.PASSWORD_RESET, - 'email': userEmail - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendPasswordResetEmail('user@example.com', {}) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendPasswordResetEmail RPC call with tenant ID. - */ -function testSendPasswordResetEmail_success_tenantId() { - var userEmail = 'user@example.com'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.PASSWORD_RESET, - 'email': userEmail, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.sendPasswordResetEmail('user@example.com', {}) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendPasswordResetEmail RPC call with custom locale and no - * action code settings. - */ -function testSendPasswordResetEmail_success_customLocale_noActionCode() { - var userEmail = 'user@example.com'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.PASSWORD_RESET, - 'email': userEmail - }), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'es' - }, - delay, - expectedResponse); - rpcHandler.updateCustomLocaleHeader('es'); - rpcHandler.sendPasswordResetEmail('user@example.com', {}) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid email sendPasswordResetEmail error. - */ -function testSendPasswordResetEmail_invalidEmailError() { - // Test when invalid email is passed in getOobCode request. - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.sendPasswordResetEmail('user.invalid', {}) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_EMAIL), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response sendPasswordResetEmail error. - */ -function testSendPasswordResetEmail_unknownServerResponse() { - var userEmail = 'user@example.com'; - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.PASSWORD_RESET, - 'email': userEmail - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendPasswordResetEmail(userEmail, {}).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side sendPasswordResetEmail error. - */ -function testSendPasswordResetEmail_caughtServerError() { - var userEmail = 'user@example.com'; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/getOobConfirmationCode?key=apiKey'; - var requestBody = { - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.PASSWORD_RESET, - 'email': userEmail - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.EMAIL_NOT_FOUND] = - fireauth.authenum.Error.USER_DELETED; - errorMap[fireauth.RpcHandler.ServerError.RESET_PASSWORD_EXCEED_LIMIT] = - fireauth.authenum.Error.TOO_MANY_ATTEMPTS_TRY_LATER; - errorMap[fireauth.RpcHandler.ServerError.INVALID_RECIPIENT_EMAIL] = - fireauth.authenum.Error.INVALID_RECIPIENT_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SENDER] = - fireauth.authenum.Error.INVALID_SENDER; - errorMap[fireauth.RpcHandler.ServerError.INVALID_MESSAGE_PAYLOAD] = - fireauth.authenum.Error.INVALID_MESSAGE_PAYLOAD; - - // Action code settings related errors. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CONTINUE_URI] = - fireauth.authenum.Error.INVALID_CONTINUE_URI; - errorMap[fireauth.RpcHandler.ServerError.MISSING_ANDROID_PACKAGE_NAME] = - fireauth.authenum.Error.MISSING_ANDROID_PACKAGE_NAME; - errorMap[fireauth.RpcHandler.ServerError.MISSING_IOS_BUNDLE_ID] = - fireauth.authenum.Error.MISSING_IOS_BUNDLE_ID; - errorMap[fireauth.RpcHandler.ServerError.UNAUTHORIZED_DOMAIN] = - fireauth.authenum.Error.UNAUTHORIZED_DOMAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_DYNAMIC_LINK_DOMAIN] = - fireauth.authenum.Error.INVALID_DYNAMIC_LINK_DOMAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.sendPasswordResetEmail(userEmail, {}); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful sendEmailVerification RPC call with action code settings. - */ -function testSendEmailVerification_success_actionCodeSettings() { - var idToken = 'ID_TOKEN'; - var userEmail = 'user@example.com'; - var expectedResponse = { - 'email': userEmail - }; - var additionalRequestData = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_EMAIL, - 'idToken': idToken, - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendEmailVerification(idToken, additionalRequestData) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendEmailVerification RPC call with no action code settings. - */ -function testSendEmailVerification_success_noActionCodeSettings() { - var idToken = 'ID_TOKEN'; - var userEmail = 'user@example.com'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_EMAIL, - 'idToken': idToken - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendEmailVerification(idToken, {}) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendEmailVerification RPC call with tenant ID. - */ -function testSendEmailVerification_success_tenantId() { - var idToken = 'ID_TOKEN'; - var userEmail = 'user@example.com'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_EMAIL, - 'idToken': idToken, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.sendEmailVerification(idToken, {}) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendEmailVerification RPC call with custom locale and no - * action code settings. - */ -function testSendEmailVerification_success_customLocale_noActionCodeSettings() { - var idToken = 'ID_TOKEN'; - var userEmail = 'user@example.com'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_EMAIL, - 'idToken': idToken - }), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'ar' - }, - delay, - expectedResponse); - rpcHandler.updateCustomLocaleHeader('ar'); - rpcHandler.sendEmailVerification(idToken, {}) - .then(function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response sendEmailVerification error. - */ -function testSendEmailVerification_unknownServerResponse() { - var idToken = 'ID_TOKEN'; - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_EMAIL, - 'idToken': idToken - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendEmailVerification(idToken, {}).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side sendEmailVerification error. - */ -function testSendEmailVerification_caughtServerError() { - var idToken = 'ID_TOKEN'; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/getOobConfirmationCode?key=apiKey'; - var requestBody = { - 'requestType': fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_EMAIL, - 'idToken': idToken - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.EMAIL_NOT_FOUND] = - fireauth.authenum.Error.USER_DELETED; - - // Action code settings related errors. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CONTINUE_URI] = - fireauth.authenum.Error.INVALID_CONTINUE_URI; - errorMap[fireauth.RpcHandler.ServerError.MISSING_ANDROID_PACKAGE_NAME] = - fireauth.authenum.Error.MISSING_ANDROID_PACKAGE_NAME; - errorMap[fireauth.RpcHandler.ServerError.MISSING_IOS_BUNDLE_ID] = - fireauth.authenum.Error.MISSING_IOS_BUNDLE_ID; - errorMap[fireauth.RpcHandler.ServerError.UNAUTHORIZED_DOMAIN] = - fireauth.authenum.Error.UNAUTHORIZED_DOMAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_DYNAMIC_LINK_DOMAIN] = - fireauth.authenum.Error.INVALID_DYNAMIC_LINK_DOMAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.sendEmailVerification(idToken, {}); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful verifyBeforeUpdateEmail RPC call with action code settings. - */ -function testVerifyBeforeUpdateEmail_success_actionCodeSettings() { - var idToken = 'ID_TOKEN'; - var newEmail = 'newUser@example.com'; - var currentEmail = 'user@example.com'; - var expectedResponse = { - 'email': currentEmail - }; - var additionalRequestData = { - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': - fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_AND_CHANGE_EMAIL, - 'idToken': idToken, - 'newEmail': newEmail, - 'continueUrl': 'https://www.example.com/?state=abc', - 'iOSBundleId': 'com.example.ios', - 'androidPackageName': 'com.example.android', - 'androidInstallApp': true, - 'androidMinimumVersion': '12', - 'canHandleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyBeforeUpdateEmail(idToken, newEmail, additionalRequestData) - .then(function(email) { - assertEquals(currentEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyBeforeUpdateEmail RPC call with no action code - * settings. - */ -function testVerifyBeforeUpdateEmail_success_noActionCodeSettings() { - var idToken = 'ID_TOKEN'; - var newEmail = 'newUser@example.com'; - var currentEmail = 'user@example.com'; - var expectedResponse = { - 'email': currentEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': - fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_AND_CHANGE_EMAIL, - 'idToken': idToken, - 'newEmail': newEmail - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyBeforeUpdateEmail(idToken, newEmail, {}) - .then(function(email) { - assertEquals(currentEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyBeforeUpdateEmail RPC call with custom locale and no - * action code settings. - */ -function testVerifyBeforeUpdateEmail_success_customLocale() { - var idToken = 'ID_TOKEN'; - var newEmail = 'newUser@example.com'; - var currentEmail = 'user@example.com'; - var expectedResponse = { - 'email': currentEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': - fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_AND_CHANGE_EMAIL, - 'idToken': idToken, - 'newEmail': newEmail - }), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'ar' - }, - delay, - expectedResponse); - rpcHandler.updateCustomLocaleHeader('ar'); - rpcHandler.verifyBeforeUpdateEmail(idToken, newEmail, {}) - .then(function(email) { - assertEquals(currentEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response verifyBeforeUpdateEmail error. - */ -function testVerifyBeforeUpdateEmail_unknownServerResponse() { - var idToken = 'ID_TOKEN'; - var newEmail = 'newUser@example.com'; - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobCon' + - 'firmationCode?key=apiKey', - 'POST', - goog.json.serialize({ - 'requestType': - fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_AND_CHANGE_EMAIL, - 'idToken': idToken, - 'newEmail': newEmail - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyBeforeUpdateEmail(idToken, newEmail, {}) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side verifyBeforeUpdateEmail error. - */ -function testVerifyBeforeUpdateEmail_caughtServerError() { - var idToken = 'ID_TOKEN'; - var newEmail = 'newUser@example.com'; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/getOobConfirmationCode?key=apiKey'; - var requestBody = { - 'requestType': - fireauth.RpcHandler.GetOobCodeRequestType.VERIFY_AND_CHANGE_EMAIL, - 'idToken': idToken, - 'newEmail': newEmail - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.EMAIL_NOT_FOUND] = - fireauth.authenum.Error.USER_DELETED; - errorMap[fireauth.RpcHandler.ServerError.EMAIL_EXISTS] = - fireauth.authenum.Error.EMAIL_EXISTS; - errorMap[fireauth.RpcHandler.ServerError.CREDENTIAL_TOO_OLD_LOGIN_AGAIN] = - fireauth.authenum.Error.CREDENTIAL_TOO_OLD_LOGIN_AGAIN; - - // Action code settings related errors. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CONTINUE_URI] = - fireauth.authenum.Error.INVALID_CONTINUE_URI; - errorMap[fireauth.RpcHandler.ServerError.MISSING_ANDROID_PACKAGE_NAME] = - fireauth.authenum.Error.MISSING_ANDROID_PACKAGE_NAME; - errorMap[fireauth.RpcHandler.ServerError.MISSING_IOS_BUNDLE_ID] = - fireauth.authenum.Error.MISSING_IOS_BUNDLE_ID; - errorMap[fireauth.RpcHandler.ServerError.UNAUTHORIZED_DOMAIN] = - fireauth.authenum.Error.UNAUTHORIZED_DOMAIN; - errorMap[fireauth.RpcHandler.ServerError.INVALID_DYNAMIC_LINK_DOMAIN] = - fireauth.authenum.Error.INVALID_DYNAMIC_LINK_DOMAIN; - - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyBeforeUpdateEmail(idToken, newEmail, {}); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful confirmPasswordReset RPC call. - */ -function testConfirmPasswordReset_success() { - var userEmail = 'user@example.com'; - var newPassword = 'newPass'; - var code = 'PASSWORD_RESET_OOB_CODE'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPass' + - 'word?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code, - 'newPassword': newPassword - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.confirmPasswordReset(code, newPassword).then( - function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful confirmPasswordReset RPC call with tenant ID. - */ -function testConfirmPasswordReset_success_tenantId() { - var userEmail = 'user@example.com'; - var newPassword = 'newPass'; - var code = 'PASSWORD_RESET_OOB_CODE'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPass' + - 'word?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code, - 'newPassword': newPassword, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.confirmPasswordReset(code, newPassword).then( - function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -function testConfirmPasswordReset_missingCode() { - asyncTestCase.waitForSignals(1); - rpcHandler.confirmPasswordReset('', 'myPassword') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OOB_CODE), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response confirmPasswordReset error. - */ -function testConfirmPasswordReset_unknownServerResponse() { - var newPassword = 'newPass'; - var code = 'PASSWORD_RESET_OOB_CODE'; - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPass' + - 'word?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code, - 'newPassword': newPassword - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.confirmPasswordReset(code, newPassword).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side confirmPasswordReset error. - */ -function testConfirmPasswordReset_caughtServerError() { - var newPassword = 'newPass'; - var code = 'PASSWORD_RESET_OOB_CODE'; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/resetPassword?key=apiKey'; - var requestBody = { - 'oobCode': code, - 'newPassword': newPassword - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.EXPIRED_OOB_CODE] = - fireauth.authenum.Error.EXPIRED_OOB_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_OOB_CODE] = - fireauth.authenum.Error.INVALID_OOB_CODE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_OOB_CODE] = - fireauth.authenum.Error.INTERNAL_ERROR; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.confirmPasswordReset(code, newPassword); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful checkActionCode RPC call. - */ -function testCheckActionCode_success() { - var code = 'REVOKE_EMAIL_OOB_CODE'; - var expectedResponse = { - 'email': 'user@example.com', - 'newEmail': 'fake@example.com', - 'requestType': 'PASSWORD_RESET' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPass' + - 'word?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.checkActionCode(code).then( - function(info) { - assertObjectEquals(expectedResponse, info); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful checkActionCode RPC call with tenant ID. - */ -function testCheckActionCode_success_tenantId() { - var code = 'PASSWORD_RESET_OOB_CODE'; - var expectedResponse = { - 'email': 'user@example.com', - 'requestType': 'PASSWORD_RESET' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPass' + - 'word?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.checkActionCode(code).then( - function(info) { - assertObjectEquals(expectedResponse, info); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful checkActionCode RPC call for email sign-in. - */ -function testCheckActionCode_emailSignIn_success() { - var code = 'EMAIL_SIGNIN_CODE'; - // Email field is empty for EMAIL_SIGNIN. - var expectedResponse = { - 'requestType': 'EMAIL_SIGNIN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPass' + - 'word?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.checkActionCode(code).then( - function(info) { - assertObjectEquals(expectedResponse, info); - asyncTestCase.signal(); - }); -} - - -function testCheckActionCode_missingCode() { - asyncTestCase.waitForSignals(1); - rpcHandler.checkActionCode('') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OOB_CODE), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response checkActionCode error (empty response). - */ -function testCheckActionCode_uncaughtServerError() { - var code = 'REVOKE_EMAIL_OOB_CODE'; - // Required fields missing in response. - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPass' + - 'word?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.checkActionCode(code).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response checkActionCode error (email only returned). - */ -function testCheckActionCode_uncaughtServerError() { - var code = 'REVOKE_EMAIL_OOB_CODE'; - // Required requestType field missing in response. - var expectedResponse = { - 'email': 'user@example.com', - 'newEmail': 'fake@example.com' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPass' + - 'word?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.checkActionCode(code).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side checkActionCode error. - */ -function testCheckActionCode_caughtServerError() { - var code = 'REVOKE_EMAIL_OOB_CODE'; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/resetPassword?key=apiKey'; - var requestBody = { - 'oobCode': code - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.EXPIRED_OOB_CODE] = - fireauth.authenum.Error.EXPIRED_OOB_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_OOB_CODE] = - fireauth.authenum.Error.INVALID_OOB_CODE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_OOB_CODE] = - fireauth.authenum.Error.INTERNAL_ERROR; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.checkActionCode(code); - }, errorMap, expectedUrl, requestBody); -} - - -function testApplyActionCode_success() { - var userEmail = 'user@example.com'; - var code = 'EMAIL_VERIFICATION_OOB_CODE'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'setAccountInfo?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.applyActionCode(code).then( - function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful applyActionCode RPC call with tenant ID. - */ -function testApplyActionCode_success_tenantId() { - var userEmail = 'user@example.com'; - var code = 'EMAIL_VERIFICATION_OOB_CODE'; - var expectedResponse = { - 'email': userEmail - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'setAccountInfo?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code, - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.applyActionCode(code).then( - function(email) { - assertEquals(userEmail, email); - asyncTestCase.signal(); - }); -} - - -function testApplyActionCode_missingCode() { - asyncTestCase.waitForSignals(1); - rpcHandler.applyActionCode('') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_OOB_CODE), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response applyActionCode error. - */ -function testApplyActionCode_unknownServerResponse() { - var code = 'EMAIL_VERIFICATION_OOB_CODE'; - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'setAccountInfo?key=apiKey', - 'POST', - goog.json.serialize({ - 'oobCode': code - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.applyActionCode(code).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side applyActionCode error. - */ -function testApplyActionCode_caughtServerError() { - var code = 'EMAIL_VERIFICATION_OOB_CODE'; - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/setAccountInfo?key=apiKey'; - var requestBody = { - 'oobCode': code - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.EXPIRED_OOB_CODE] = - fireauth.authenum.Error.EXPIRED_OOB_CODE; - errorMap[fireauth.RpcHandler.ServerError.EMAIL_NOT_FOUND] = - fireauth.authenum.Error.USER_DELETED; - errorMap[fireauth.RpcHandler.ServerError.INVALID_OOB_CODE] = - fireauth.authenum.Error.INVALID_OOB_CODE; - errorMap[fireauth.RpcHandler.ServerError.USER_DISABLED] = - fireauth.authenum.Error.USER_DISABLED; - - assertServerErrorsAreHandled(function() { - return rpcHandler.applyActionCode(code); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests serialize_ method. - */ -function testSerialize() { - var obj1 = { - 'a': 1, - 'b': 'gegg', - 'c': [1, 2, 4] - }; - assertEquals(goog.json.serialize(obj1), fireauth.RpcHandler.serialize_(obj1)); - var obj2 = { - 'a': 1, - 'b': null, - 'c': undefined, - 'd': '', - 'e': 0, - 'f': false - }; - // null and undefined should be removed. - assertEquals( - goog.json.serialize({'a': 1, 'd': '', 'e': 0, 'f': false}), - fireauth.RpcHandler.serialize_(obj2)); -} - - -/** - * Tests successful deleteLinkedAccounts RPC call. - */ -function testDeleteLinkedAccounts_success() { - var expectedResponse = { - 'email': 'user@example.com', - 'providerUserInfo': [ - {'providerId': 'google.com'} - ] - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'deleteProvider': ['github.com', 'facebook.com'] - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.deleteLinkedAccounts('ID_TOKEN', ['github.com', 'facebook.com']) - .then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request deleteLinkedAccounts error. - */ -function testDeleteLinkedAccounts_invalidRequestError() { - // Test when request is invalid. - asyncTestCase.waitForSignals(1); - rpcHandler.deleteLinkedAccounts('ID_TOKEN', 'google.com').thenCatch( - function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server caught deleteLinkedAccounts errors. - */ -function testDeleteLinkedAccounts_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/setAccountInfo?key=apiKey'; - var requestBody = { - 'idToken': 'ID_TOKEN', - 'deleteProvider': ['github.com', 'facebook.com'] - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.USER_NOT_FOUND] = - fireauth.authenum.Error.TOKEN_EXPIRED; - - assertServerErrorsAreHandled(function() { - return rpcHandler.deleteLinkedAccounts( - 'ID_TOKEN', ['github.com', 'facebook.com']); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful updateProfile request. - */ -function testUpdateProfile_success() { - var expectedResponse = { - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default.png' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default.png', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateProfile('ID_TOKEN', { - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default.png' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testUpdateProfile_blankFields() { - var expectedResponse = { - // We test here that a response without email is a valid response. - 'email': '', - 'displayName': '' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'displayName': '', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateProfile('ID_TOKEN', { - 'displayName': '' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testUpdateProfile_omittedFields() { - var expectedResponse = { - 'email': 'uid123@fake.com', - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default.png' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'displayName': 'John Doe', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateProfile('ID_TOKEN', { - 'displayName': 'John Doe' - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testUpdateProfile_deleteFields() { - var expectedResponse = { - 'email': 'uid123@fake.com', - 'displayName': 'John Doe' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'displayName': 'John Doe', - 'deleteAttribute': ['PHOTO_URL'], - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateProfile('ID_TOKEN', { - 'displayName': 'John Doe', - 'photoUrl': null - }).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server caught updateProfile error. - */ -function testUpdateProfile_error() { - var serverResponse = { - 'error': {'message': fireauth.RpcHandler.ServerError.INTERNAL_ERROR} - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - goog.json.serialize(serverResponse)); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default.png', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.updateProfile('ID_TOKEN', { - 'displayName': 'John Doe', - 'photoUrl': 'http://abs.twimg.com/sticky/default.png' - }).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUpdateEmail_success() { - var expectedResponse = { - 'email': 'newuser@example.com' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.updateEmail('ID_TOKEN', 'newuser@example.com') - .then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'email': 'newuser@example.com', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); -} - - -function testUpdateEmail_customLocale_success() { - var expectedResponse = { - 'email': 'newuser@example.com' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.updateCustomLocaleHeader('tr'); - rpcHandler.updateEmail('ID_TOKEN', 'newuser@example.com') - .then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'email': 'newuser@example.com', - 'returnSecureToken': true - }), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'tr' - }, - delay, - expectedResponse); -} - - -function testUpdateEmail_invalidEmail() { - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INVALID_EMAIL); - asyncTestCase.waitForSignals(1); - rpcHandler.updateEmail('ID_TOKEN', 'newuser.invalid') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -function testUpdateEmail_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/setAccountInfo?key=apiKey'; - var email = 'newuser@example.com'; - var idToken = 'ID_TOKEN'; - var requestBody = { - 'idToken': 'ID_TOKEN', - 'email': email, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_EMAIL] = - fireauth.authenum.Error.INVALID_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_ID_TOKEN] = - fireauth.authenum.Error.INVALID_AUTH; - errorMap[fireauth.RpcHandler.ServerError.EMAIL_CHANGE_NEEDS_VERIFICATION] = - fireauth.authenum.Error.EMAIL_CHANGE_NEEDS_VERIFICATION; - - assertServerErrorsAreHandled(function() { - return rpcHandler.updateEmail(idToken, email); - }, errorMap, expectedUrl, requestBody); -} - - -function testUpdatePassword_success() { - var expectedResponse = { - 'email': 'user@example.com', - 'idToken': 'idToken' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'password': 'newPassword', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updatePassword('ID_TOKEN', 'newPassword') - .then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testUpdatePassword_noPassword() { - asyncTestCase.waitForSignals(1); - rpcHandler.updatePassword('ID_TOKEN', '') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.WEAK_PASSWORD), - error); - asyncTestCase.signal(); - }); -} - - -function testUpdateEmailAndPassword_success() { - var expectedResponse = { - 'email': 'user@example.com', - 'idToken': 'idToken' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccount' + - 'Info?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'email': 'me@gmail.com', - 'password': 'newPassword', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateEmailAndPassword('ID_TOKEN', 'me@gmail.com', 'newPassword') - .then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -function testUpdateEmailAndPassword_noEmail() { - asyncTestCase.waitForSignals(1); - rpcHandler.updateEmailAndPassword('ID_TOKEN', '', 'newPassword') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_EMAIL), - error); - asyncTestCase.signal(); - }); -} - - -function testUpdateEmailAndPassword_noPassword() { - asyncTestCase.waitForSignals(1); - rpcHandler.updateEmailAndPassword('ID_TOKEN', 'me@gmail.com', '') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.WEAK_PASSWORD), - error); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignInForLinking_success() { - var expectedResponse = {'idToken': 'ID_TOKEN'}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSi' + - 'gnin?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'email': 'user@example.com', - 'oobCode': 'OTP_CODE', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.emailLinkSignInForLinking( - 'ID_TOKEN', 'user@example.com', 'OTP_CODE') - .then(function(response) { - assertEquals('ID_TOKEN', response['idToken']); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignInForLinking_serverCaughtError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/' + - 'relyingparty/emailLinkSignin?key=apiKey'; - var email = 'user@example.com'; - var oobCode = 'OTP_CODE'; - var id_token = 'ID_TOKEN'; - var requestBody = { - 'idToken': 'ID_TOKEN', - 'email': email, - 'oobCode': oobCode, - 'returnSecureToken': true - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_EMAIL] = - fireauth.authenum.Error.INVALID_EMAIL; - errorMap[fireauth.RpcHandler.ServerError.TOO_MANY_ATTEMPTS_TRY_LATER] = - fireauth.authenum.Error.TOO_MANY_ATTEMPTS_TRY_LATER; - errorMap[fireauth.RpcHandler.ServerError.USER_DISABLED] = - fireauth.authenum.Error.USER_DISABLED; - - assertServerErrorsAreHandled(function() { - return rpcHandler.emailLinkSignInForLinking(id_token, email, oobCode); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests invalid server response emailLinkSignInForLinking error. - */ -function testEmailLinkSignInForLinking_unknownServerResponse() { - // Test when server returns unexpected response with no error message. - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSi' + - 'gnin?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'email': 'user@example.com', - 'oobCode': 'OTP_CODE', - 'returnSecureToken': true - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - {}); - rpcHandler.emailLinkSignInForLinking( - 'ID_TOKEN', 'user@example.com', 'OTP_CODE') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignInForLinking_emptyActionCodeError() { - // Test when empty action code is passed in emailLinkSignInForLinking request. - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.emailLinkSignInForLinking('ID_TOKEN', 'user@example.com', '') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignInForLinking_invalidEmailError() { - // Test when invalid email is passed in emailLinkSignInForLinking request. - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.emailLinkSignInForLinking( - 'ID_TOKEN', 'user.invalid', 'OTP_CODE') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INVALID_EMAIL), - error); - asyncTestCase.signal(); - }); -} - - -function testEmailLinkSignInForLinking_emptyIdTokenError() { - // Test when empty ID token is passed in emailLinkSignInForLinking request. - asyncTestCase.waitForSignals(1); - // Test when request is invalid. - rpcHandler.emailLinkSignInForLinking( - '', 'user@example.com', 'OTP_CODE') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -function testInvokeRpc() { - asyncTestCase.waitForSignals(3); - var request = { - 'myRequestKey': 'myRequestValue', - 'myOtherRequestKey': 'myOtherRequestValue' - }; - var response = { - 'myResponseKey': 'myResponseValue', - 'myOtherResponseKey': 'myOtherResponseValue' - }; - - var rpcMethod = { - endpoint: 'myEndpoint', - requestRequiredFields: ['myRequestKey'], - requestValidator: function(actualRequest) { - assertObjectEquals(request, actualRequest); - asyncTestCase.signal(); - }, - responseValidator: function(actualResponse) { - assertObjectEquals(response, actualResponse); - asyncTestCase.signal(); - } - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'myEndpoint?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - response); - rpcHandler.invokeRpc(rpcMethod, request).then(function(actualResponse) { - assertObjectEquals(response, actualResponse); - asyncTestCase.signal(); - }); -} - - -function testInvokeRpc_useIdentityPlatformEndpoint() { - // Test RPC method with useIdentityPlatformEndpoint set to true uses - // identity platform endpoint. - asyncTestCase.waitForSignals(1); - var request = { - 'myRequestKey': 'myRequestValue', - 'myOtherRequestKey': 'myOtherRequestValue' - }; - var response = { - 'myResponseKey': 'myResponseValue', - 'myOtherResponseKey': 'myOtherResponseValue' - }; - // Define RPC method in identity platform. - var rpcMethod = { - endpoint: 'accounts/mfaEnrollment:start', - requireTenantId: true, - useIdentityPlatformEndpoint: true - }; - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:start?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - response); - rpcHandler.invokeRpc(rpcMethod, request).then(function(actualResponse) { - assertObjectEquals(response, actualResponse); - asyncTestCase.signal(); - }); -} - - -function testInvokeRpc_requireTenantId() { - asyncTestCase.waitForSignals(3); - rpcHandler.updateTenantId('123456789012'); - var request = { - 'myRequestKey': 'myRequestValue', - 'myOtherRequestKey': 'myOtherRequestValue' - }; - var response = { - 'myResponseKey': 'myResponseValue', - 'myOtherResponseKey': 'myOtherResponseValue' - }; - - var rpcMethod = { - endpoint: 'myEndpoint', - requestRequiredFields: ['myRequestKey'], - requestValidator: function(actualRequest) { - assertObjectEquals(request, actualRequest); - asyncTestCase.signal(); - }, - responseValidator: function(actualResponse) { - assertObjectEquals(response, actualResponse); - asyncTestCase.signal(); - }, - requireTenantId: true - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'myEndpoint?key=apiKey', - 'POST', - goog.json.serialize({ - 'myRequestKey': 'myRequestValue', - 'myOtherRequestKey': 'myOtherRequestValue', - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - response); - rpcHandler.invokeRpc(rpcMethod, request).then(function(actualResponse) { - assertObjectEquals(response, actualResponse); - asyncTestCase.signal(); - }); -} - - -function testInvokeRpc_httpMethod() { - asyncTestCase.waitForSignals(1); - var request = {}; - var rpcMethod = { - endpoint: 'myEndpoint', - httpMethod: fireauth.RpcHandler.HttpMethod.GET - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'myEndpoint?key=apiKey', - 'GET', - undefined, - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - {}); - rpcHandler.invokeRpc(rpcMethod, request) - .then(function() { - asyncTestCase.signal(); - }); -} - - -function testInvokeRpc_requiredFields() { - // The XHR should not be sent if there was a problem with the request. - stubs.replace(fireauth.RpcHandler.prototype, 'sendXhr_', fail); - asyncTestCase.waitForSignals(1); - var request = { - 'myRequestKey': 'myRequestValue', - 'myOtherRequestKey': 'myOtherRequestValue' - }; - var rpcMethod = { - endpoint: 'myEndpoint', - requestRequiredFields: ['myRequestKey', 'keyThatIsNotThere'], - requestValidator: function() {} - }; - rpcHandler.invokeRpc(rpcMethod, request).then(fail, function(actualError) { - fireauth.common.testHelper.assertErrorEquals(new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR), actualError); - asyncTestCase.signal(); - }); -} - - -function testInvokeRpc_requestError() { - // The XHR should not be sent if there was a problem with the request. - stubs.replace(fireauth.RpcHandler.prototype, 'sendXhr_', fail); - asyncTestCase.waitForSignals(2); - var request = { - 'myRequestKey': 'myRequestValue', - 'myOtherRequestKey': 'myOtherRequestValue' - }; - - var error = {'name': 'myRequestError'}; - var rpcMethod = { - endpoint: 'myEndpoint', - requestValidator: function(actualRequest) { - assertObjectEquals(request, actualRequest); - asyncTestCase.signal(); - throw error; - } - }; - rpcHandler.invokeRpc(rpcMethod, request).then(fail, function(actualError) { - assertObjectEquals(error, actualError); - asyncTestCase.signal(); - }); -} - - -function testInvokeRpc_responseError() { - asyncTestCase.waitForSignals(2); - var request = {}; - var response = { - 'myResponseKey': 'myResponseValue', - 'myOtherResponseKey': 'myOtherResponseValue' - }; - var error = {'name': 'myResponseError'}; - var rpcMethod = { - endpoint: 'myEndpoint', - responseValidator: function(actualResponse) { - assertObjectEquals(response, actualResponse); - asyncTestCase.signal(); - throw error; - } - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'myEndpoint?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - response); - rpcHandler.invokeRpc(rpcMethod, request).then(fail, function(actualError) { - assertObjectEquals(error, actualError); - asyncTestCase.signal(); - }); -} - - -function testInvokeRpc_responseField() { - asyncTestCase.waitForSignals(1); - var request = {}; - var response = { - 'someOtherField': 'unimportantInfo', - 'theFieldWeWant': 'importantInfo' - }; - var rpcMethod = { - endpoint: 'myEndpoint', - responseField: 'theFieldWeWant' - }; - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + - 'myEndpoint?key=apiKey', - 'POST', - goog.json.serialize(request), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - response); - rpcHandler.invokeRpc(rpcMethod, request).then(function(actualValue) { - assertObjectEquals('importantInfo', actualValue); - asyncTestCase.signal(); - }); -} - - -/** - * Test getAdditionalScopes_ for passing scopes in createAuthUri request. - */ -function testGetAdditionalScopes() { - var scopes = fireauth.RpcHandler.getAdditionalScopes_('google.com'); - assertNull(scopes); - scopes = fireauth.RpcHandler.getAdditionalScopes_( - 'google.com', ['scope1', 'scope2', 'scope3']); - assertEquals( - goog.json.serialize({ - 'google.com': 'scope1,scope2,scope3' - }), - scopes); -} - - -/** - * Tests successful getAuthUri request. - */ -function testGetAuthUri_success() { - var expectedCustomParameters = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - var expectedResponse = { - 'authUri': 'https://accounts.google.com', - 'providerId': 'google.com', - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'SESSION_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuth' + - 'Uri?key=apiKey', - 'POST', - goog.json.serialize({ - 'identifier': 'user@example.com', - 'providerId': 'google.com', - 'continueUri': 'http://localhost/widget', - 'customParameter': expectedCustomParameters, - 'oauthScope': goog.json.serialize({ - 'google.com': 'scope1,scope2,scope3' - }) - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.getAuthUri( - 'google.com', - 'http://localhost/widget', - expectedCustomParameters, - ['scope1', 'scope2', 'scope3'], - 'user@example.com').then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful getAuthUri request with tenant ID. - */ -function testGetAuthUri_success_tenantId() { - var expectedCustomParameters = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - var expectedResponse = { - 'authUri': 'https://accounts.google.com', - 'providerId': 'google.com', - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'SESSION_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuth' + - 'Uri?key=apiKey', - 'POST', - goog.json.serialize({ - 'identifier': 'user@example.com', - 'providerId': 'google.com', - 'continueUri': 'http://localhost/widget', - 'customParameter': expectedCustomParameters, - 'oauthScope': goog.json.serialize({ - 'google.com': 'scope1,scope2,scope3' - }), - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.getAuthUri( - 'google.com', - 'http://localhost/widget', - expectedCustomParameters, - ['scope1', 'scope2', 'scope3'], - 'user@example.com').then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful getAuthUri request for a SAML Auth flow. - */ -function testGetAuthUri_success_saml() { - var expectedCustomParameters = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - var expectedResponse = { - 'authUri': 'https://www.example.com/samlp/?SAMLRequest=1234567890', - 'providerId': 'saml.provider', - 'registered': false, - 'sessionId': 'SESSION_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuth' + - 'Uri?key=apiKey', - 'POST', - goog.json.serialize({ - 'providerId': 'saml.provider', - 'continueUri': 'http://localhost/widget' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.getAuthUri( - 'saml.provider', - 'http://localhost/widget', - // Custom parameters should be ignored. - expectedCustomParameters, - // Scopes should be ignored. - ['scope1', 'scope2', 'scope3'], - null).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful getAuthUri request with tenant ID for a SAML Auth flow. - */ -function testGetAuthUri_success_saml_tenantId() { - var expectedCustomParameters = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - var expectedResponse = { - 'authUri': 'https://www.example.com/samlp/?SAMLRequest=1234567890', - 'providerId': 'saml.provider', - 'registered': false, - 'sessionId': 'SESSION_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuth' + - 'Uri?key=apiKey', - 'POST', - goog.json.serialize({ - 'providerId': 'saml.provider', - 'continueUri': 'http://localhost/widget', - 'tenantId': '123456789012' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.getAuthUri( - 'saml.provider', - 'http://localhost/widget', - // Custom parameters should be ignored. - expectedCustomParameters, - // Scopes should be ignored. - ['scope1', 'scope2', 'scope3'], - null).then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests getAuthUri request with no continue URI. - */ -function testGetAuthUri_error_missingContinueUri() { - var expectedCustomParameters = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - var expectedError = - new fireauth.AuthError(fireauth.authenum.Error.MISSING_CONTINUE_URI); - asyncTestCase.waitForSignals(1); - rpcHandler.getAuthUri( - 'saml.provider', - null, - // Custom parameters should be ignored. - expectedCustomParameters, - // Scopes should be ignored. - ['scope1', 'scope2', 'scope3'], - 'user@example.com').thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests getAuthUri request with no provider ID. - */ -function testGetAuthUri_error_missingProviderId() { - var expectedCustomParameters = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'A provider ID must be provided in the request.'); - asyncTestCase.waitForSignals(1); - rpcHandler.getAuthUri( - // No provider ID. - null, - 'http://localhost/widget', - // Custom parameters should be ignored. - expectedCustomParameters, - // Scopes should be ignored. - ['scope1', 'scope2', 'scope3'], - 'user@example.com').thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side getAuthUri error. - */ -function testGetAuthUri_caughtServerError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/createAuthUri?key=apiKey'; - var requestBody = { - 'providerId': 'abc.com', - 'continueUri': 'http://localhost/widget', - 'customParameter': {} - }; - var errorMap = {}; - // All related server errors for getAuthUri. - errorMap[fireauth.RpcHandler.ServerError.INVALID_PROVIDER_ID] = - fireauth.authenum.Error.INVALID_PROVIDER_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.getAuthUri( - 'abc.com', - 'http://localhost/widget'); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful getAuthUri request with Google provider and sessionId. - */ -function testGetAuthUri_googleProvider_withSessionId_success() { - var expectedCustomParameters = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - var expectedResponse = { - 'authUri': 'https://accounts.google.com', - 'providerId': 'google.com', - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'SESSION_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuth' + - 'Uri?key=apiKey', - 'POST', - goog.json.serialize({ - 'identifier': 'user@example.com', - 'providerId': 'google.com', - 'continueUri': 'http://localhost/widget', - 'customParameter': expectedCustomParameters, - 'oauthScope': goog.json.serialize({ - 'google.com': 'scope1,scope2,scope3' - }), - 'sessionId': 'SESSION_ID', - 'authFlowType': 'CODE_FLOW' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.getAuthUri( - 'google.com', - 'http://localhost/widget', - expectedCustomParameters, - ['scope1', 'scope2', 'scope3'], - 'user@example.com', - 'SESSION_ID').then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful getAuthUri request with other provider and sessionId. - */ -function testGetAuthUri_otherProvider_withSessionId_success() { - var expectedResponse = { - 'authUri': 'https://facebook.com/login', - 'providerId': 'facebook.com', - 'registered': true, - 'sessionId': 'SESSION_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuth' + - 'Uri?key=apiKey', - 'POST', - goog.json.serialize({ - 'providerId': 'facebook.com', - 'continueUri': 'http://localhost/widget', - 'customParameter': {}, - 'oauthScope': goog.json.serialize({ - 'facebook.com': 'scope1,scope2,scope3' - }), - 'sessionId': 'SESSION_ID' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.getAuthUri( - 'facebook.com', - 'http://localhost/widget', - undefined, - ['scope1', 'scope2', 'scope3'], - undefined, - 'SESSION_ID').then(function(response) { - assertObjectEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server caught getAuthUri error. - */ -function testGetAuthUri_error() { - var serverResponse = { - 'error': {'message': fireauth.RpcHandler.ServerError.INTERNAL_ERROR} - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - goog.json.serialize(serverResponse)); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuth' + - 'Uri?key=apiKey', - 'POST', - goog.json.serialize({ - 'providerId': 'google.com', - 'continueUri': 'http://localhost/widget', - 'customParameter': {} - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - serverResponse); - rpcHandler.getAuthUri( - 'google.com', - 'http://localhost/widget').thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests getAuthUri response without authUri field. - */ -function testGetAuthUri_error_noAuthUri() { - var expectedCustomParameters = { - 'hd': 'example.com', - 'login_hint': 'user@example.com' - }; - // getAuthUri response without authUri field. - var expectedResponse = { - 'providerId': 'google.com', - 'registered': true, - 'forExistingProvider': true, - 'sessionId': 'SESSION_ID' - }; - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'Unable to determine the authorization endpoint for the specified '+ - 'provider. This may be an issue in the provider configuration.'); - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuth' + - 'Uri?key=apiKey', - 'POST', - goog.json.serialize({ - 'identifier': 'user@example.com', - 'providerId': 'google.com', - 'continueUri': 'http://localhost/widget', - 'customParameter': expectedCustomParameters, - 'oauthScope': goog.json.serialize({ - 'google.com': 'scope1,scope2,scope3' - }) - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.getAuthUri( - 'google.com', - 'http://localhost/widget', - expectedCustomParameters, - ['scope1', 'scope2', 'scope3'], - 'user@example.com').thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful startPhoneMfaEnrollment RPC call. - */ -function testStartPhoneMfaEnrollment_success() { - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneEnrollmentInfo': { - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN' - } - }; - var expectedResponse = { - 'phoneSessionInfo': { - 'sessionInfo': 'SESSION_INFO' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:start?key=apiKey', - 'POST', - goog.json.serialize(enrollmentRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.startPhoneMfaEnrollment(enrollmentRequest) - .then(function(sessionInfo) { - assertEquals( - expectedResponse['phoneSessionInfo']['sessionInfo'], sessionInfo); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request startPhoneMfaEnrollment error for a missing phone - * number. - */ -function testStartPhoneMfaEnrollment_invalidRequest_missingPhoneNumber() { - // Missing phone number in request. - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneEnrollmentInfo': { - 'recaptchaToken': 'RECAPTCHA_TOKEN' - } - }; - asyncTestCase.waitForSignals(1); - rpcHandler.startPhoneMfaEnrollment(enrollmentRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.MISSING_PHONE_NUMBER), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request startPhoneMfaEnrollment error for a missing recaptcha - * token. - */ -function testStartPhoneMfaEnrollment_invalidRequest_missingRecaptchaToken() { - // Missing recaptcha token in request. - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneEnrollmentInfo': { - 'phoneNumber': '+15551234567' - } - }; - asyncTestCase.waitForSignals(1); - rpcHandler.startPhoneMfaEnrollment(enrollmentRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.MISSING_APP_CREDENTIAL), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response StartPhoneMfaEnrollment error. - */ -function testStartPhoneMfaEnrollment_unknownServerResponse() { - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneEnrollmentInfo': { - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN' - } - }; - // No sessionInfo returned. - var expectedResponse = { - 'phoneSessionInfo': { - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:start?key=apiKey', - 'POST', - goog.json.serialize(enrollmentRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.startPhoneMfaEnrollment(enrollmentRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side StartPhoneMfaEnrollment error. - */ -function testStartPhoneMfaEnrollment_caughtServerError() { - var expectedUrl = identityPlatformEndpoint + - 'accounts/mfaEnrollment:start?key=apiKey'; - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneEnrollmentInfo': { - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN' - } - }; - var errorMap = {}; - // All related server errors for StartPhoneMfaEnrollment. - errorMap[fireauth.RpcHandler.ServerError.CAPTCHA_CHECK_FAILED] = - fireauth.authenum.Error.CAPTCHA_CHECK_FAILED; - errorMap[fireauth.RpcHandler.ServerError.INVALID_APP_CREDENTIAL] = - fireauth.authenum.Error.INVALID_APP_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_PHONE_NUMBER] = - fireauth.authenum.Error.INVALID_PHONE_NUMBER; - errorMap[fireauth.RpcHandler.ServerError.MISSING_APP_CREDENTIAL] = - fireauth.authenum.Error.MISSING_APP_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.MISSING_PHONE_NUMBER] = - fireauth.authenum.Error.MISSING_PHONE_NUMBER; - errorMap[fireauth.RpcHandler.ServerError.QUOTA_EXCEEDED] = - fireauth.authenum.Error.QUOTA_EXCEEDED; - errorMap[fireauth.RpcHandler.ServerError.REJECTED_CREDENTIAL] = - fireauth.authenum.Error.REJECTED_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.SECOND_FACTOR_LIMIT_EXCEEDED] = - fireauth.authenum.Error.SECOND_FACTOR_LIMIT_EXCEEDED; - errorMap[fireauth.RpcHandler.ServerError.SECOND_FACTOR_EXISTS] = - fireauth.authenum.Error.SECOND_FACTOR_EXISTS; - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_FIRST_FACTOR] = - fireauth.authenum.Error.UNSUPPORTED_FIRST_FACTOR; - errorMap[fireauth.RpcHandler.ServerError.UNVERIFIED_EMAIL] = - fireauth.authenum.Error.UNVERIFIED_EMAIL; - - assertServerErrorsAreHandled(function() { - return rpcHandler.startPhoneMfaEnrollment(enrollmentRequest); - }, errorMap, expectedUrl, enrollmentRequest); -} - - -/** - * Tests successful finalizePhoneMfaEnrollment RPC call using an SMS code. - */ -function testFinalizePhoneMfaEnrollment_success() { - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:finalize?key=apiKey', - 'POST', - goog.json.serialize(enrollmentRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponse); - rpcHandler.finalizePhoneMfaEnrollment(enrollmentRequest) - .then(function(response) { - assertEquals(tokenResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful finalizePhoneMfaEnrollment RPC call using an SMS code with - * display name. - */ -function testFinalizePhoneMfaEnrollment_success_displayName() { - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'displayName': 'Work phone number', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:finalize?key=apiKey', - 'POST', - goog.json.serialize(enrollmentRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponse); - rpcHandler.finalizePhoneMfaEnrollment(enrollmentRequest) - .then(function(response) { - assertEquals(tokenResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request finalizePhoneMfaEnrollment error for a missing - * sessionInfo. - */ -function testFinalizePhoneMfaEnrollment_invalidRequest_missingSessionInfo() { - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneVerificationInfo': { - 'code': '123456' - } - }; - asyncTestCase.waitForSignals(1); - rpcHandler.finalizePhoneMfaEnrollment(enrollmentRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.MISSING_SESSION_INFO), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request finalizePhoneMfaEnrollment error for a missing code. - */ -function testFinalizePhoneMfaEnrollment_invalidRequest_missingCode() { - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO' - } - }; - asyncTestCase.waitForSignals(1); - rpcHandler.finalizePhoneMfaEnrollment(enrollmentRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response finalizePhoneMfaEnrollment error. - */ -function testFinalizePhoneMfaEnrollment_unknownServerResponse() { - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - } - }; - // No idToken returned. - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:finalize?key=apiKey', - 'POST', - goog.json.serialize(enrollmentRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.finalizePhoneMfaEnrollment(enrollmentRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side finalizePhoneMfaEnrollment error. - */ -function testFinalizePhoneMfaEnrollment_caughtServerError() { - var expectedUrl = identityPlatformEndpoint + - 'accounts/mfaEnrollment:finalize?key=apiKey'; - var enrollmentRequest = { - 'idToken': 'ID_TOKEN', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - } - }; - var errorMap = {}; - // All related server errors for finalizePhoneMfaEnrollment. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CODE] = - fireauth.authenum.Error.INVALID_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SESSION_INFO] = - fireauth.authenum.Error.INVALID_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TEMPORARY_PROOF] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CODE] = - fireauth.authenum.Error.MISSING_CODE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_SESSION_INFO] = - fireauth.authenum.Error.MISSING_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.SESSION_EXPIRED] = - fireauth.authenum.Error.CODE_EXPIRED; - errorMap[fireauth.RpcHandler.ServerError.REJECTED_CREDENTIAL] = - fireauth.authenum.Error.REJECTED_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.SECOND_FACTOR_LIMIT_EXCEEDED] = - fireauth.authenum.Error.SECOND_FACTOR_LIMIT_EXCEEDED; - errorMap[fireauth.RpcHandler.ServerError.SECOND_FACTOR_EXISTS] = - fireauth.authenum.Error.SECOND_FACTOR_EXISTS; - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_FIRST_FACTOR] = - fireauth.authenum.Error.UNSUPPORTED_FIRST_FACTOR; - errorMap[fireauth.RpcHandler.ServerError.UNVERIFIED_EMAIL] = - fireauth.authenum.Error.UNVERIFIED_EMAIL; - - assertServerErrorsAreHandled(function() { - return rpcHandler.finalizePhoneMfaEnrollment(enrollmentRequest); - }, errorMap, expectedUrl, enrollmentRequest); -} - - -/** - * Tests successful startPhoneMfaSignIn RPC call. - */ -function testStartPhoneMfaSignIn_success() { - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_ID', - 'phoneSignInInfo': { - 'recaptchaToken': 'RECAPTCHA_TOKEN' - } - }; - var expectedResponse = { - 'phoneResponseInfo': { - 'sessionInfo': 'SESSION_INFO' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaSignIn:start?key=apiKey', - 'POST', - goog.json.serialize(signInRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.startPhoneMfaSignIn(signInRequest).then(function(sessionInfo) { - assertEquals( - expectedResponse['phoneResponseInfo']['sessionInfo'], sessionInfo); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request startPhoneMfaSignIn error for a missing recaptha token. - */ -function testStartPhoneMfaSignIn_invalidRequest_missingRecaptchaToken() { - // Missing recaptha token in request. - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_ID', - 'phoneSignInInfo': {} - }; - asyncTestCase.waitForSignals(1); - rpcHandler.startPhoneMfaSignIn(signInRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_APP_CREDENTIAL), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response StartPhoneMfaSignIn error. - */ -function testStartPhoneMfaSignIn_unknownServerResponse() { - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_ID', - 'phoneSignInInfo': { - 'recaptchaToken': 'RECAPTCHA_TOKEN' - } - }; - // No sessionInfo returned. - var expectedResponse = { - 'phoneResponseInfo': { - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaSignIn:start?key=apiKey', - 'POST', - goog.json.serialize(signInRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.startPhoneMfaSignIn(signInRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side StartPhoneMfaSignIn error. - */ -function testStartPhoneMfaSignIn_caughtServerError() { - var expectedUrl = identityPlatformEndpoint + - 'accounts/mfaSignIn:start?key=apiKey'; - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'mfaEnrollmentId': 'ENROLLMENT_ID', - 'phoneSignInInfo': { - 'recaptchaToken': 'RECAPTCHA_TOKEN' - } - }; - var errorMap = {}; - // All related server errors for StartPhoneMfaSignIn. - errorMap[fireauth.RpcHandler.ServerError.CAPTCHA_CHECK_FAILED] = - fireauth.authenum.Error.CAPTCHA_CHECK_FAILED; - errorMap[fireauth.RpcHandler.ServerError.INVALID_APP_CREDENTIAL] = - fireauth.authenum.Error.INVALID_APP_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_PHONE_NUMBER] = - fireauth.authenum.Error.INVALID_PHONE_NUMBER; - errorMap[fireauth.RpcHandler.ServerError.MISSING_APP_CREDENTIAL] = - fireauth.authenum.Error.MISSING_APP_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.MISSING_PHONE_NUMBER] = - fireauth.authenum.Error.MISSING_PHONE_NUMBER; - errorMap[fireauth.RpcHandler.ServerError.QUOTA_EXCEEDED] = - fireauth.authenum.Error.QUOTA_EXCEEDED; - errorMap[fireauth.RpcHandler.ServerError.REJECTED_CREDENTIAL] = - fireauth.authenum.Error.REJECTED_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_MFA_PENDING_CREDENTIAL] = - fireauth.authenum.Error.INVALID_MFA_PENDING_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.MFA_ENROLLMENT_NOT_FOUND] = - fireauth.authenum.Error.MFA_ENROLLMENT_NOT_FOUND; - errorMap[fireauth.RpcHandler.ServerError.MISSING_MFA_ENROLLMENT_ID] = - fireauth.authenum.Error.MISSING_MFA_ENROLLMENT_ID; - errorMap[fireauth.RpcHandler.ServerError.MISSING_MFA_PENDING_CREDENTIAL] = - fireauth.authenum.Error.MISSING_MFA_PENDING_CREDENTIAL; - - assertServerErrorsAreHandled(function() { - return rpcHandler.startPhoneMfaSignIn(signInRequest); - }, errorMap, expectedUrl, signInRequest); -} - - -/** - * Tests successful finalizePhoneMfaSignIn RPC call using an SMS code. - */ -function testFinalizePhoneMfaSignIn_success() { - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - } - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaSignIn:finalize?key=apiKey', - 'POST', - goog.json.serialize(signInRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponse); - rpcHandler.finalizePhoneMfaSignIn(signInRequest).then(function(response) { - assertEquals(tokenResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request finalizePhoneMfaSignIn error for a missing sessionInfo. - */ -function testFinalizePhoneMfaSignIn_invalidRequest_missingSessionInfo() { - // SessionInfo is missing in request. - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'phoneVerificationInfo': { - 'code': '123456' - } - }; - rpcHandler.finalizePhoneMfaSignIn(signInRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_SESSION_INFO), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request finalizePhoneMfaSignIn error for a missing code. - */ -function testFinalizePhoneMfaSignIn_invalidRequest_missingCode() { - // Code is missing in request. - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO' - } - }; - asyncTestCase.waitForSignals(1); - rpcHandler.finalizePhoneMfaSignIn(signInRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response finalizePhoneMfaSignIn error. - */ -function testFinalizePhoneMfaSignIn_unknownServerResponse() { - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - } - }; - // No idToken returned. - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaSignIn:finalize?key=apiKey', - 'POST', - goog.json.serialize(signInRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.finalizePhoneMfaSignIn(signInRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side finalizePhoneMfaSignIn error. - */ -function testFinalizePhoneMfaSignIn_caughtServerError() { - var expectedUrl = identityPlatformEndpoint + - 'accounts/mfaSignIn:finalize?key=apiKey'; - var signInRequest = { - 'mfaPendingCredential': 'MFA_PENDING_CREDENTIAL', - 'phoneVerificationInfo': { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - } - }; - var errorMap = {}; - // All related server errors for finalizePhoneMfaSignIn. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CODE] = - fireauth.authenum.Error.INVALID_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SESSION_INFO] = - fireauth.authenum.Error.INVALID_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TEMPORARY_PROOF] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CODE] = - fireauth.authenum.Error.MISSING_CODE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_SESSION_INFO] = - fireauth.authenum.Error.MISSING_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.SESSION_EXPIRED] = - fireauth.authenum.Error.CODE_EXPIRED; - errorMap[fireauth.RpcHandler.ServerError.REJECTED_CREDENTIAL] = - fireauth.authenum.Error.REJECTED_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_MFA_PENDING_CREDENTIAL] = - fireauth.authenum.Error.INVALID_MFA_PENDING_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.MISSING_MFA_PENDING_CREDENTIAL] = - fireauth.authenum.Error.MISSING_MFA_PENDING_CREDENTIAL; - - assertServerErrorsAreHandled(function() { - return rpcHandler.finalizePhoneMfaSignIn(signInRequest); - }, errorMap, expectedUrl, signInRequest); -} - - -/** - * Tests a successful WithdrawMfa RPC call where the user session is maintained. - * A successful call to WithdrawMfa will either return a new idToken and - * refreshToken if the user session is maintained or no tokens if the session - * is revoked (this situation is tested below). - */ -function testWithdrawMfa_success_withMaintainedUserSession() { - var expectedResponse = { - 'idToken': 'ID_TOKEN', - 'refreshToken': 'REFRESH_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:withdraw?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'mfaEnrollmentId': 'MFA_ENROLLMENT_ID' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, delay, expectedResponse); - - rpcHandler.withdrawMfa('ID_TOKEN', 'MFA_ENROLLMENT_ID') - .then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests a successful WithdrawMfa RPC call where the user session is revoked. - * In this case, the successful call returns an empty object indicating the user - * has been signed out. - */ -function testWithdrawMfa_success_withRevokedUserSession() { - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:withdraw?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'mfaEnrollmentId': 'MFA_ENROLLMENT_ID' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - - rpcHandler.withdrawMfa('ID_TOKEN', 'MFA_ENROLLMENT_ID') - .then(function(response) { - assertEquals(expectedResponse, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests an invalid request WithdrawMfa with a missing mfaEnrollmentId. - */ -function testWithdrawMfa_invalidRequest_missingMfaEnrollmentId() { - asyncTestCase.waitForSignals(1); - - // Tests with a missing mfaEnrollmentId in the request. - rpcHandler.withdrawMfa('ID_TOKEN', '') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests an invalid request WithdrawMfa with a missing idToken. - */ -function testWithdrawMfa_invalidRequest_missingIdToken() { - asyncTestCase.waitForSignals(1); - - // Tests with a missing idToken in the request. - rpcHandler.withdrawMfa('', 'MFA_ENROLLMENT_ID') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests an invalid response from WithdrawMfa. - */ -function testWithdrawMfa_unknownServerResponse() { - // No refreshToken returned - var expectedResponse = { - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - identityPlatformEndpoint + 'accounts/mfaEnrollment:withdraw?key=apiKey', - 'POST', - goog.json.serialize({ - 'idToken': 'ID_TOKEN', - 'mfaEnrollmentId': 'MFA_ENROLLMENT_ID' - }), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - - rpcHandler.withdrawMfa('ID_TOKEN', 'MFA_ENROLLMENT_ID') - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests handling of server side WithdrawMfa errors. - */ -function testWithdrawMfa_caughtServerError() { - var expectedUrl = identityPlatformEndpoint + - 'accounts/mfaEnrollment:withdraw?key=apiKey'; - var idToken = 'ID_TOKEN'; - var mfaEnrollmentId = 'MFA_ENROLLMENT_ID'; - var requestBody = { - 'idToken': idToken, - 'mfaEnrollmentId': mfaEnrollmentId - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.INVALID_ID_TOKEN] = - fireauth.authenum.Error.INVALID_AUTH; - errorMap[fireauth.RpcHandler.ServerError.MFA_ENROLLMENT_NOT_FOUND] = - fireauth.authenum.Error.MFA_ENROLLMENT_NOT_FOUND; - errorMap[fireauth.RpcHandler.ServerError.MISSING_MFA_ENROLLMENT_ID] = - fireauth.authenum.Error.MISSING_MFA_ENROLLMENT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.withdrawMfa(idToken, mfaEnrollmentId); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful sendVerificationCode RPC call. - */ -function testSendVerificationCode_success() { - var expectedRequest = { - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN' - }; - var expectedResponse = { - 'sessionInfo': 'SESSION_INFO' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/sendVerifi' + - 'cationCode?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendVerificationCode(expectedRequest).then(function(sessionInfo) { - assertEquals(expectedResponse['sessionInfo'], sessionInfo); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful sendVerificationCode RPC call with tenant ID. - */ -function testSendVerificationCode_success_tenantId() { - var expectedRequest = { - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN', - 'tenantId': '123456789012' - }; - var expectedResponse = { - 'sessionInfo': 'SESSION_INFO' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/sendVerifi' + - 'cationCode?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.sendVerificationCode({ - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN' - }).then(function(sessionInfo) { - assertEquals(expectedResponse['sessionInfo'], sessionInfo); - asyncTestCase.signal(); - }); -} - - -function testSendVerificationCode_unsupportedTenantOperation() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/sendVerificationCode?key=apiKey'; - var requestBody = { - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN', - 'tenantId': '123456789012' - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_TENANT_OPERATION] = - fireauth.authenum.Error.UNSUPPORTED_TENANT_OPERATION; - rpcHandler.updateTenantId('123456789012'); - assertServerErrorsAreHandled(function() { - return rpcHandler.sendVerificationCode({ - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN' - }); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests invalid request sendVerificationCode error for a missing phone number. - */ -function testSendVerificationCode_invalidRequest_missingPhoneNumber() { - var expectedRequest = { - 'recaptchaToken': 'RECAPTCHA_TOKEN' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.sendVerificationCode(expectedRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request sendVerificationCode error for a missing reCAPTCHA - * token. - */ -function testSendVerificationCode_invalidRequest_missingRecaptchaToken() { - var expectedRequest = { - 'phoneNumber': '+15551234567' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.sendVerificationCode(expectedRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response sendVerificationCode error. - */ -function testSendVerificationCode_unknownServerResponse() { - var expectedRequest = { - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN' - }; - // No sessionInfo returned. - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/sendVerifi' + - 'cationCode?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.sendVerificationCode(expectedRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side sendVerificationCode error. - */ -function testSendVerificationCode_caughtServerError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/sendVerificationCode?key=apiKey'; - var requestBody = { - 'phoneNumber': '+15551234567', - 'recaptchaToken': 'RECAPTCHA_TOKEN' - }; - var errorMap = {}; - // All related server errors for sendVerificationCode. - errorMap[fireauth.RpcHandler.ServerError.CAPTCHA_CHECK_FAILED] = - fireauth.authenum.Error.CAPTCHA_CHECK_FAILED; - errorMap[fireauth.RpcHandler.ServerError.INVALID_APP_CREDENTIAL] = - fireauth.authenum.Error.INVALID_APP_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_PHONE_NUMBER] = - fireauth.authenum.Error.INVALID_PHONE_NUMBER; - errorMap[fireauth.RpcHandler.ServerError.MISSING_APP_CREDENTIAL] = - fireauth.authenum.Error.MISSING_APP_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.MISSING_PHONE_NUMBER] = - fireauth.authenum.Error.MISSING_PHONE_NUMBER; - errorMap[fireauth.RpcHandler.ServerError.QUOTA_EXCEEDED] = - fireauth.authenum.Error.QUOTA_EXCEEDED; - errorMap[fireauth.RpcHandler.ServerError.REJECTED_CREDENTIAL] = - fireauth.authenum.Error.REJECTED_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.sendVerificationCode(requestBody); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful verifyPhoneNumber RPC call using an SMS code. - */ -function testVerifyPhoneNumber_success_usingCode() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponseWithExpiresIn); - rpcHandler.verifyPhoneNumber(expectedRequest).then(function(response) { - assertEquals(tokenResponseWithExpiresIn, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyPhoneNumber RPC call using an SMS code and passing - * custom locale. - */ -function testVerifyPhoneNumber_success_customLocale_usingCode() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - { - 'Content-Type': 'application/json', - 'X-Firebase-Locale': 'ru' - }, - delay, - tokenResponseWithExpiresIn); - rpcHandler.updateCustomLocaleHeader('ru'); - rpcHandler.verifyPhoneNumber(expectedRequest).then(function(response) { - assertEquals(tokenResponseWithExpiresIn, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyPhoneNumber RPC call using a temporary proof. - */ -function testVerifyPhoneNumber_success_usingTemporaryProof() { - var expectedRequest = { - 'phoneNumber': '+16505550101', - 'temporaryProof': 'TEMPORARY_PROOF' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponseWithExpiresIn); - rpcHandler.verifyPhoneNumber(expectedRequest).then(function(response) { - assertEquals(tokenResponseWithExpiresIn, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyPhoneNumber RPC call with tenant ID. - */ -function testVerifyPhoneNumber_success_tenantId() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'tenantId': '123456789012' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponseWithExpiresIn); - rpcHandler.updateTenantId('123456789012'); - rpcHandler.verifyPhoneNumber({ - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }).then(function(response) { - assertEquals(tokenResponseWithExpiresIn, response); - asyncTestCase.signal(); - }); -} - - -function testVerifyPhoneNumber_unsupportedTenantOperation() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/verifyPhoneNumber?key=apiKey'; - var requestBody = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'tenantId': '123456789012' - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_TENANT_OPERATION] = - fireauth.authenum.Error.UNSUPPORTED_TENANT_OPERATION; - rpcHandler.updateTenantId('123456789012'); - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyPhoneNumber({ - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests a verifyPhoneNumber RPC call using a temporary proof without a - * phone number. - */ -function testVerifyPhoneNumber_error_noPhoneNumber() { - var expectedRequest = { - 'temporaryProof': 'TEMPORARY_PROOF' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumber(expectedRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests a verifyPhoneNumber RPC call using a phone number without a - * temporary proof. - */ -function testVerifyPhoneNumber_error_noTemporaryProof() { - var expectedRequest = { - 'phoneNumber': '+16505550101' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumber(expectedRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPhoneNumber error for a missing sessionInfo. - */ -function testVerifyPhoneNumber_invalidRequest_missingSessionInfo() { - var expectedRequest = { - 'code': '123456' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumber(expectedRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_SESSION_INFO), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPhoneNumber error for a missing code. - */ -function testVerifyPhoneNumber_invalidRequest_missingCode() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumber(expectedRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response verifyPhoneNumber error. - */ -function testVerifyPhoneNumber_unknownServerResponse() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }; - // No idToken returned. - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyPhoneNumber(expectedRequest).thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side verifyPhoneNumber error. - */ -function testVerifyPhoneNumber_caughtServerError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/verifyPhoneNumber?key=apiKey'; - var requestBody = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }; - var errorMap = {}; - // All related server errors for verifyPhoneNumber. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CODE] = - fireauth.authenum.Error.INVALID_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SESSION_INFO] = - fireauth.authenum.Error.INVALID_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TEMPORARY_PROOF] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CODE] = - fireauth.authenum.Error.MISSING_CODE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_SESSION_INFO] = - fireauth.authenum.Error.MISSING_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.SESSION_EXPIRED] = - fireauth.authenum.Error.CODE_EXPIRED; - errorMap[fireauth.RpcHandler.ServerError.REJECTED_CREDENTIAL] = - fireauth.authenum.Error.REJECTED_CREDENTIAL; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TENANT_ID] = - fireauth.authenum.Error.INVALID_TENANT_ID; - - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyPhoneNumber(requestBody); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests successful verifyPhoneNumberForLinking RPC call using an SMS code. - */ -function testVerifyPhoneNumberForLinking_success_usingCode() { - // Temporary proof not used for linking as it corresponds to an existing - // credential that will fail when being linked or updated on another account. - // No need to test for it. - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponseWithExpiresIn); - rpcHandler.verifyPhoneNumberForLinking(expectedRequest) - .then(function(response) { - assertEquals(tokenResponseWithExpiresIn, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPhoneNumberForLinking error for a missing - * sessionInfo. - */ -function testVerifyPhoneNumberForLinking_invalidRequest_missingSessionInfo() { - var expectedRequest = { - 'code': '123456', - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumberForLinking(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.MISSING_SESSION_INFO), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPhoneNumberForLinking error for a missing code. - */ -function testVerifyPhoneNumberForLinking_invalidRequest_missingCode() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'idToken': 'ID_TOKEN' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumberForLinking(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPhoneNumberForLinking error for a missing ID - * token. - */ -function testVerifyPhoneNumberForLinking_invalidRequest_missingIdToken() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumberForLinking(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response verifyPhoneNumberForLinking error. - */ -function testVerifyPhoneNumberForLinking_unknownServerResponse() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'idToken': 'ID_TOKEN' - }; - // No idToken returned. - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyPhoneNumberForLinking(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side verifyPhoneNumber error. - */ -function testVerifyPhoneNumberForLinking_caughtServerError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/verifyPhoneNumber?key=apiKey'; - var requestBody = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'idToken': 'ID_TOKEN' - }; - var errorMap = {}; - // All related server errors for verifyPhoneNumberForLinking. - errorMap[fireauth.RpcHandler.ServerError.INVALID_CODE] = - fireauth.authenum.Error.INVALID_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SESSION_INFO] = - fireauth.authenum.Error.INVALID_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TEMPORARY_PROOF] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CODE] = - fireauth.authenum.Error.MISSING_CODE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_SESSION_INFO] = - fireauth.authenum.Error.MISSING_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.SESSION_EXPIRED] = - fireauth.authenum.Error.CODE_EXPIRED; - - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyPhoneNumberForLinking(requestBody); - }, errorMap, expectedUrl, requestBody); -} - - - -/** - * Tests that when verifyPhoneNumber returns a temporaryProof, an appropriate - * credential object is created and attached to the error. - */ -function testVerifyPhoneNumberForLinking_credentialAlreadyInUseError() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'idToken': 'ID_TOKEN' - }; - var expectedResponse = { - 'temporaryProof': 'theTempProof', - 'phoneNumber': '+16505550101' - }; - - var credential = fireauth.AuthProvider.getCredentialFromResponse( - expectedResponse); - var expectedError = new fireauth.AuthErrorWithCredential( - fireauth.authenum.Error.CREDENTIAL_ALREADY_IN_USE, - { - phoneNumber: '+16505550101', - credential: credential - }); - - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyPhoneNumberForLinking(expectedRequest) - .then(fail, function(error) { - assertTrue(error instanceof fireauth.AuthErrorWithCredential); - fireauth.common.testHelper.assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyPhoneNumberForExisting RPC call using an SMS code. - */ -function testVerifyPhoneNumberForExisting_success_usingCode() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'operation': 'REAUTH' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponseWithExpiresIn); - rpcHandler.verifyPhoneNumberForExisting(expectedRequest) - .then(function(response) { - assertEquals(tokenResponseWithExpiresIn, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyPhoneNumberForExisting RPC call using a temporary - * proof. - */ -function testVerifyPhoneNumberForExisting_success_usingTemporaryProof() { - var expectedRequest = { - 'phoneNumber': '+16505550101', - 'temporaryProof': 'TEMPORARY_PROOF', - 'operation': 'REAUTH' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponseWithExpiresIn); - rpcHandler.verifyPhoneNumberForExisting(expectedRequest) - .then(function(response) { - assertEquals(tokenResponseWithExpiresIn, response); - asyncTestCase.signal(); - }); -} - - -/** - * Tests successful verifyPhoneNumberForExisting RPC call with tenant ID. - */ -function testVerifyPhoneNumberForExisting_success_tenantId() { - var requestBody = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'operation': 'REAUTH', - 'tenantId': 'TENANT_ID' - }; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(requestBody), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - tokenResponseWithExpiresIn); - rpcHandler.updateTenantId('TENANT_ID'); - rpcHandler.verifyPhoneNumberForExisting({ - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }).then(function(response) { - assertEquals(tokenResponseWithExpiresIn, response); - asyncTestCase.signal(); - }); -} - - -function testVerifyPhoneNumberForExisting_unsupportedTenantOperation() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/verifyPhoneNumber?key=apiKey'; - var requestBody = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'operation': 'REAUTH', - 'tenantId': 'TENANT_ID' - }; - var errorMap = {}; - errorMap[fireauth.RpcHandler.ServerError.UNSUPPORTED_TENANT_OPERATION] = - fireauth.authenum.Error.UNSUPPORTED_TENANT_OPERATION; - rpcHandler.updateTenantId('TENANT_ID'); - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyPhoneNumberForExisting({ - 'sessionInfo': 'SESSION_INFO', - 'code': '123456' - }); - }, errorMap, expectedUrl, requestBody); -} - - -/** - * Tests invalid request verifyPhoneNumberForExisting error for a missing - * sessionInfo. - */ -function testVerifyPhoneNumberForExisting_invalidRequest_missingSessionInfo() { - var expectedRequest = { - 'code': '123456', - 'operation': 'REAUTH' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumberForExisting(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError( - fireauth.authenum.Error.MISSING_SESSION_INFO), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPhoneNumberForExisting error for a missing code. - */ -function testVerifyPhoneNumberForExisting_invalidRequest_missingCode() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'operation': 'REAUTH' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumberForExisting(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.MISSING_CODE), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPhoneNumberForExisting error for a missing - * phoneNumber. - */ -function testVerifyPhoneNumberForExisting_invalidRequest_missingPhoneNumber() { - var expectedRequest = { - 'temporaryProof': 'TEMPORARY_PROOF', - 'operation': 'REAUTH' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumberForExisting(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid request verifyPhoneNumberForExisting error for a missing - * temporary proof. - */ -function testVerifyPhoneNumberForExisting_invalidRequest_missingTempProof() { - var expectedRequest = { - 'phoneNumber': '+16505550101', - 'operation': 'REAUTH' - }; - asyncTestCase.waitForSignals(1); - rpcHandler.verifyPhoneNumberForExisting(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests invalid response verifyPhoneNumberForExisting error. - */ -function testVerifyPhoneNumberForExisting_unknownServerResponse() { - var expectedRequest = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'operation': 'REAUTH' - }; - // No idToken returned. - var expectedResponse = {}; - asyncTestCase.waitForSignals(1); - assertSendXhrAndRunCallback( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhon' + - 'eNumber?key=apiKey', - 'POST', - goog.json.serialize(expectedRequest), - fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, - delay, - expectedResponse); - rpcHandler.verifyPhoneNumberForExisting(expectedRequest) - .thenCatch(function(error) { - fireauth.common.testHelper.assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), - error); - asyncTestCase.signal(); - }); -} - - -/** - * Tests server side verifyPhoneNumberForExisting error. - */ -function testVerifyPhoneNumberForExisting_caughtServerError() { - var expectedUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyin' + - 'gparty/verifyPhoneNumber?key=apiKey'; - var requestBody = { - 'sessionInfo': 'SESSION_INFO', - 'code': '123456', - 'operation': 'REAUTH' - }; - var errorMap = {}; - // All related server errors for verifyPhoneNumberForExisting. - - // This should be overridden from the default error mapping. - errorMap[fireauth.RpcHandler.ServerError.USER_NOT_FOUND] = - fireauth.authenum.Error.USER_DELETED; - - errorMap[fireauth.RpcHandler.ServerError.INVALID_CODE] = - fireauth.authenum.Error.INVALID_CODE; - errorMap[fireauth.RpcHandler.ServerError.INVALID_SESSION_INFO] = - fireauth.authenum.Error.INVALID_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.INVALID_TEMPORARY_PROOF] = - fireauth.authenum.Error.INVALID_IDP_RESPONSE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_CODE] = - fireauth.authenum.Error.MISSING_CODE; - errorMap[fireauth.RpcHandler.ServerError.MISSING_SESSION_INFO] = - fireauth.authenum.Error.MISSING_SESSION_INFO; - errorMap[fireauth.RpcHandler.ServerError.SESSION_EXPIRED] = - fireauth.authenum.Error.CODE_EXPIRED; - - assertServerErrorsAreHandled(function() { - return rpcHandler.verifyPhoneNumberForExisting(requestBody); - }, errorMap, expectedUrl, requestBody); -} diff --git a/packages/auth/test/storage/asyncstorage_test.js b/packages/auth/test/storage/asyncstorage_test.js deleted file mode 100644 index d1db7190fd1..00000000000 --- a/packages/auth/test/storage/asyncstorage_test.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.AsyncStorageTest'); - -goog.require('fireauth.storage.AsyncStorage'); -goog.require('fireauth.storage.Storage'); -/** @suppress {extraRequire} */ -goog.require('fireauth.storage.testHelper'); -goog.require('fireauth.storage.testHelper.FakeAsyncStorage'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.storage.AsyncStorageTest'); - - -var stubs = new goog.testing.PropertyReplacer(); -var storage; - - -function setUp() { - storage = new fireauth.storage.AsyncStorage( - new fireauth.storage.testHelper.FakeAsyncStorage()); -} - - -function tearDown() { - storage = null; - stubs.reset(); -} - - -function testBasicStorageOperations() { - assertEquals(fireauth.storage.Storage.Type.ASYNC_STORAGE, storage.type); - return assertBasicStorageOperations(storage); -} - - -function testDifferentTypes() { - return assertDifferentTypes(storage); -} - - -function testNotAvailable() { - stubs.replace(firebase.INTERNAL, 'reactNative', {}); - var error = assertThrows(function() { new fireauth.storage.AsyncStorage(); }); - assertEquals('auth/internal-error', error.code); -} diff --git a/packages/auth/test/storage/factory_test.js b/packages/auth/test/storage/factory_test.js deleted file mode 100644 index c5825b62fb9..00000000000 --- a/packages/auth/test/storage/factory_test.js +++ /dev/null @@ -1,214 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.FactoryTest'); - -goog.require('fireauth.storage.AsyncStorage'); -goog.require('fireauth.storage.Factory'); -goog.require('fireauth.storage.Factory.EnvConfig'); -goog.require('fireauth.storage.HybridIndexedDB'); -goog.require('fireauth.storage.InMemoryStorage'); -goog.require('fireauth.storage.IndexedDB'); -goog.require('fireauth.storage.LocalStorage'); -goog.require('fireauth.storage.NullStorage'); -goog.require('fireauth.storage.SessionStorage'); -goog.require('fireauth.storage.Storage'); -/** @suppress {extraRequire} */ -goog.require('fireauth.storage.testHelper'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.storage.FactoryTest'); - - -var stubs = new goog.testing.PropertyReplacer(); - - -function setUp() { - // Simulate storage not persisted with indexedDB. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() { - return false; - }); -} - - -function tearDown() { - stubs.reset(); -} - - -function testGetStorage_browser_temporary() { - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.BROWSER); - assertTrue(factory.makeTemporaryStorage() instanceof - fireauth.storage.SessionStorage); -} - - -function testGetStorage_browser_persistent_localStorage() { - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() { - return false; - }); - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.BROWSER); - assertTrue(factory.makePersistentStorage() instanceof - fireauth.storage.LocalStorage); -} - - -function testGetStorage_browser_persistent_indexedDB() { - // Simulate browser to force usage of indexedDB storage. - var mock = { - type: 'indexedDB' - }; - // Record calls to HybridIndexedDB. - stubs.replace( - fireauth.storage, - 'HybridIndexedDB', - goog.testing.recordFunction(fireauth.storage.HybridIndexedDB)); - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() { - return true; - }); - stubs.replace( - fireauth.storage.IndexedDB, - 'getFireauthManager', - function() { - return mock; - }); - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.BROWSER); - assertEquals('indexedDB', factory.makePersistentStorage().type); - assertEquals(1, fireauth.storage.HybridIndexedDB.getCallCount()); - // Confirm localStorage used as fallback when indexedDB is not supported. - var fallbackStorage = - fireauth.storage.HybridIndexedDB.getLastCall().getArgument(0); - assertEquals( - fireauth.storage.Storage.Type.LOCAL_STORAGE, fallbackStorage.type); -} - - -function testGetStorage_node_temporary() { - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.NODE); - assertTrue(factory.makeTemporaryStorage() instanceof - fireauth.storage.SessionStorage); -} - - -function testGetStorage_node_persistent() { - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return false; - }); - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.NODE); - assertTrue(factory.makePersistentStorage() instanceof - fireauth.storage.LocalStorage); -} - - -function testGetStorage_reactnative_temporary() { - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.REACT_NATIVE); - assertTrue(factory.makeTemporaryStorage() instanceof - fireauth.storage.NullStorage); -} - - -function testGetStorage_reactnative_persistent() { - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return false; - }); - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.REACT_NATIVE); - assertTrue(factory.makePersistentStorage() instanceof - fireauth.storage.AsyncStorage); -} - - -function testGetStorage_worker_persistent() { - var mock = { - type: 'indexedDB' - }; - // Record calls to HybridIndexedDB. - stubs.replace( - fireauth.storage, - 'HybridIndexedDB', - goog.testing.recordFunction(fireauth.storage.HybridIndexedDB)); - // persistsStorageWithIndexedDB is true in a worker environment. - stubs.replace( - fireauth.util, - 'persistsStorageWithIndexedDB', - function() { - return true; - }); - // Simulate worker environment. - stubs.replace( - fireauth.util, - 'isWorker', - function() { - return true; - }); - // Return a mock indexeDB instance to assert the expected result of the test - // below. - stubs.replace( - fireauth.storage.IndexedDB, - 'getFireauthManager', - function() { - return mock; - }); - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.WORKER); - assertEquals('indexedDB', factory.makePersistentStorage().type); - assertEquals(1, fireauth.storage.HybridIndexedDB.getCallCount()); - // Confirm in memory storage used as fallback when indexedDB is not supported. - var fallbackStorage = - fireauth.storage.HybridIndexedDB.getLastCall().getArgument(0); - assertEquals( - fireauth.storage.Storage.Type.IN_MEMORY, fallbackStorage.type); -} - - -function testGetStorage_worker_temporary() { - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.WORKER); - assertTrue(factory.makeTemporaryStorage() instanceof - fireauth.storage.NullStorage); -} - - -function testGetStorage_inMemory() { - var factory = new fireauth.storage.Factory( - fireauth.storage.Factory.EnvConfig.BROWSER); - assertTrue(factory.makeInMemoryStorage() instanceof - fireauth.storage.InMemoryStorage); -} diff --git a/packages/auth/test/storage/hybridindexeddb_test.js b/packages/auth/test/storage/hybridindexeddb_test.js deleted file mode 100644 index 321c3e10869..00000000000 --- a/packages/auth/test/storage/hybridindexeddb_test.js +++ /dev/null @@ -1,263 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.HybridIndexedDBTest'); - -goog.require('fireauth.storage.HybridIndexedDB'); -goog.require('fireauth.storage.InMemoryStorage'); -goog.require('fireauth.storage.IndexedDB'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.Storage'); -/** @suppress {extraRequire} */ -goog.require('fireauth.storage.testHelper'); -goog.require('goog.Promise'); -goog.require('goog.events.EventType'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.events'); -goog.require('goog.testing.events.Event'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.storage.HybridIndexedDBTest'); - - -var indexeddbMockStorage; -var ignoreArgument; -var stubs = new goog.testing.PropertyReplacer(); -var mockStorage; -var storage; - - -function setUp() { - // Create new mock indexedDB storage before each test. - indexeddbMockStorage = new fireauth.storage.MockStorage(); - // IndexedDB available. - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return true; - }); - stubs.replace( - fireauth.storage.IndexedDB, - 'getFireauthManager', - function() { - indexeddbMockStorage.type = fireauth.storage.Storage.Type.INDEXEDDB; - return indexeddbMockStorage; - }); -} - - -function tearDown() { - storage = null; - stubs.reset(); -} - - -function testHybridIndexedDBStorage_basicStorageOperations() { - storage = new fireauth.storage.HybridIndexedDB( - new fireauth.storage.InMemoryStorage()); - return assertBasicStorageOperations(storage).then(function() { - assertEquals(fireauth.storage.Storage.Type.INDEXEDDB, storage.type); - }); -} - - -function testHybridIndexedDBStorage_differentTypes() { - storage = new fireauth.storage.HybridIndexedDB( - new fireauth.storage.InMemoryStorage()); - return assertDifferentTypes(storage).then(function() { - assertEquals(fireauth.storage.Storage.Type.INDEXEDDB, storage.type); - }); -} - - -function testHybridIndexedDBStorage_listeners() { - var storageEvent; - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - - storage = new fireauth.storage.HybridIndexedDB( - new fireauth.storage.InMemoryStorage()); - storage.addStorageListener(listener1); - storage.addStorageListener(listener3); - // This call will resolve when underlying storage is resolved. - return storage.get('dummy').then(function(value) { - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'myKey1'; - storageEvent.newValue = JSON.stringify('value1'); - indexeddbMockStorage.fireBrowserEvent(storageEvent); - - assertEquals(1, listener1.getCallCount()); - // Confirm expected argument passed. - // In the indexedDB case, this is an array of keys. - // If localStorage fallback is used, this would be the storage event. - assertArrayEquals( - [storageEvent.key], listener1.getLastCall().getArgument(0)); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - return storage.get('myKey1'); - }).then(function(value) { - assertEquals('value1', value); - - storage.removeStorageListener(listener3); - - storageEvent.key = 'myKey2'; - storageEvent.newValue = JSON.stringify('value2'); - indexeddbMockStorage.fireBrowserEvent(storageEvent); - - assertEquals(2, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - return storage.get('myKey2'); - }).then(function(value) { - assertEquals('value2', value); - storageEvent.key = 'myKey1'; - storageEvent.newValue = null; - indexeddbMockStorage.fireBrowserEvent(storageEvent); - - assertEquals(3, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - - return storage.get('myKey1'); - }).then(function(value) { - assertUndefined(value); - - storage.removeStorageListener(listener1); - - storageEvent.key = null; - indexeddbMockStorage.fireBrowserEvent(storageEvent); - - assertEquals(3, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - - return storage.get('myKey2'); - }).then(function(value) { - assertUndefined(value); - }); -} - - -function testHybridIndexedDBStorage_indexedDB_available() { - var inMemory = new fireauth.storage.InMemoryStorage(); - // Confirm indexedDB used when available. - storage = new fireauth.storage.HybridIndexedDB(inMemory); - return storage.set('key1', 'value1').then(function() { - // Confirm data was stored in indexedDB and not the fallback. - return indexeddbMockStorage.get('key1'); - }).then(function(value) { - assertEquals('value1', value); - return inMemory.get('key1'); - }).then(function(value) { - assertUndefined(value); - assertEquals(fireauth.storage.Storage.Type.INDEXEDDB, storage.type); - }); -} - - -function testHybridIndexedDBStorage_indexedDB_notAvailable() { - // IndexedDB not available. - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return false; - }); - var inMemory = new fireauth.storage.InMemoryStorage(); - // Confirm fallback used when indexedDB not available. - storage = new fireauth.storage.HybridIndexedDB(inMemory); - return storage.set('key1', 'value1').then(function() { - // Confirm data was stored in fallback storage. - return indexeddbMockStorage.get('key1'); - }).then(function(value) { - assertUndefined(value); - return inMemory.get('key1'); - }).then(function(value) { - assertEquals('value1', value); - assertEquals(fireauth.storage.Storage.Type.IN_MEMORY, storage.type); - }); -} - - -function testHybridIndexedDBStorage_indexedDB_readWriteError() { - stubs.replace( - fireauth.storage.IndexedDB, - 'getFireauthManager', - function() { - // Return mock indexedDB that throws an error on read/writes. - return { - set: function(key, value) { - return goog.Promise.reject(); - }, - get: function(key) { - return goog.Promise.reject(); - }, - remove: function(key) { - return goog.Promise.reject(); - } - }; - }); - var inMemory = new fireauth.storage.InMemoryStorage(); - // Confirm fallback used when indexedDB throws a read/write error. - storage = new fireauth.storage.HybridIndexedDB(inMemory); - return storage.set('key1', 'value1').then(function() { - return indexeddbMockStorage.get('key1'); - }).then(function(value) { - assertUndefined(value); - return inMemory.get('key1'); - }).then(function(value) { - assertEquals('value1', value); - assertEquals(fireauth.storage.Storage.Type.IN_MEMORY, storage.type); - }); -} - - -function testHybridIndexedDBStorage_indexedDB_noPersistence() { - stubs.replace( - fireauth.storage.IndexedDB, - 'getFireauthManager', - function() { - // Return mock indexedDB that does not persist data. - return { - set: function(key, value) { - return goog.Promise.resolve(); - }, - get: function(key) { - return goog.Promise.resolve(null); - }, - remove: function(key) { - return goog.Promise.resolve(); - } - }; - }); - var inMemory = new fireauth.storage.InMemoryStorage(); - // Confirm fallback used when indexedDB is not persisting data correctly. - storage = new fireauth.storage.HybridIndexedDB(inMemory); - return storage.set('key1', 'value1').then(function() { - return indexeddbMockStorage.get('key1'); - }).then(function(value) { - assertUndefined(value); - return inMemory.get('key1'); - }).then(function(value) { - assertEquals('value1', value); - assertEquals(fireauth.storage.Storage.Type.IN_MEMORY, storage.type); - }); -} diff --git a/packages/auth/test/storage/indexeddb_test.js b/packages/auth/test/storage/indexeddb_test.js deleted file mode 100644 index 08591f9da04..00000000000 --- a/packages/auth/test/storage/indexeddb_test.js +++ /dev/null @@ -1,829 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.IndexedDBTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.messagechannel.Receiver'); -goog.require('fireauth.messagechannel.Sender'); -goog.require('fireauth.storage.IndexedDB'); -goog.require('fireauth.storage.Storage'); -goog.require('goog.Promise'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.storage.IndexedDBTest'); - - -let mockControl; -let ignoreArgument; -const stubs = new goog.testing.PropertyReplacer(); -let db = null; -let manager; -let clock; -let indexedDBMock; -let containsObjectStore; -let deleted; - - - -function setUp() { - mockControl = new goog.testing.MockControl(); - ignoreArgument = goog.testing.mockmatchers.ignoreArgument; - mockControl.$resetAll(); - deleted = 0; - // IndexedDB not supported in IE9. - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return true; - }); - clock = new goog.testing.MockClock(true); - // Callback to run to determine whether the database constains an object - // store. - containsObjectStore = function() { - return true; - }; - indexedDBMock = { - deleteDatabase: function (dbName) { - assertEquals('firebaseLocalStorageDb', dbName); - const request = {}; - // Simulate successful deletion. - goog.Promise.resolve().then(function () { - deleted++; - db = null; - request.onsuccess(); - }); - return request; - }, - onopen_: () => { }, - open: function (dbName, version) { - assertEquals('firebaseLocalStorageDb', dbName); - assertEquals(1, version); - const dbRequest = {}; - goog.Promise.resolve().then(function () { - db = { - connectionActive: true, - objectStoreNames: { - contains: function (name) { - assertEquals('firebaseLocalStorage', name); - return containsObjectStore(); - } - }, - key: null, - store: {}, - close: function () { - if (db) { - db.connectionActive = false; - } - }, - createObjectStore: function (objectStoreName, keyPath) { - const request = { 'transaction': {} }; - assertEquals('firebaseLocalStorage', objectStoreName); - assertObjectEquals({ 'keyPath': 'fbase_key' }, keyPath); - db.key = keyPath['keyPath']; - db.store[objectStoreName] = {}; - goog.Promise.resolve().then(function () { - const event = { 'target': { 'result': db } }; - dbRequest.onsuccess(event); - }); - return request; - }, - transaction: function (objectStores, type) { - for (let i = 0; i < objectStores.length; i++) { - if (!db.store[objectStores[i]]) { - fail('Object store does not exist!'); - } - } - return { - objectStore: function (objectStoreName) { - if (!db.store[objectStoreName]) { - fail('Object store does not exist!'); - } - return { - add: function (data) { - if (!db.connectionActive) { - throw new Error( - 'Failed to execute \'transaction\' on ' + - '\'IDBDatabase\'.'); - } - const request = {}; - if (type != 'readwrite') { - fail('Invalid write operation!'); - } - if (db.store[objectStoreName][data[db.key]]) { - fail('Unable to add. Key already exists!'); - } - goog.Promise.resolve().then(function () { - db.store[objectStoreName][data[db.key]] = data; - request.onsuccess(); - }); - return request; - }, - put: function (data) { - if (!db.connectionActive) { - throw new Error( - 'Failed to execute \'transaction\' on ' + - '\'IDBDatabase\'.'); - } - const request = {}; - if (type != 'readwrite') { - fail('Invalid write operation!'); - } - if (!db.store[objectStoreName][data[db.key]]) { - fail('Unable to put. Key does not exist!'); - } - for (let subKey in data) { - db.store[objectStoreName][data[db.key]][subKey] = - data[subKey]; - } - goog.Promise.resolve().then(function () { - request.onsuccess(); - }); - return request; - }, - delete: function (keyToRemove) { - if (!db.connectionActive) { - throw new Error( - 'Failed to execute \'transaction\' on ' + - '\'IDBDatabase\'.'); - } - const request = {}; - if (type != 'readwrite') { - fail('Invalid write operation!'); - } - if (db.store[objectStoreName][keyToRemove]) { - delete db.store[objectStoreName][keyToRemove]; - } - goog.Promise.resolve().then(function () { - request.onsuccess(); - }); - return request; - }, - get: function (keyToGet, callback) { - if (!db.connectionActive) { - throw new Error( - 'Failed to execute \'transaction\' on ' + - '\'IDBDatabase\'.'); - } - const request = {}; - const data = db.store[objectStoreName][keyToGet] || null; - goog.Promise.resolve().then(function () { - const event = { 'target': { 'result': data } }; - request.onsuccess(event); - }); - return request; - }, - getAll: function () { - if (!db.connectionActive) { - throw new Error( - 'Failed to execute \'transaction\' on ' + - '\'IDBDatabase\'.'); - } - const request = {}; - const results = []; - for (let key in db.store[objectStoreName]) { - results.push(db.store[objectStoreName][key]); - } - goog.Promise.resolve().then(function () { - const event = { 'target': { 'result': results } }; - request.onsuccess(event); - }); - return request; - } - }; - } - }; - } - }; - const event = { 'target': { 'result': db } }; - dbRequest.onupgradeneeded(event); - indexedDBMock.onopen_(db); - }); - return dbRequest; - } - }; -} - - -function tearDown() { - if (manager) { - manager.removeAllStorageListeners(); - } - manager = null; - db = null; - indexedDBMock = null; - stubs.reset(); - goog.dispose(clock); - try { - mockControl.$verifyAll(); - } finally { - mockControl.$tearDown(); - } -} - - -/** - * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be - * used as Internet Explorer adds the stack trace as a property of the object. - * @param {!fireauth.AuthError} expected - * @param {!fireauth.AuthError} actual - */ -function assertErrorEquals(expected, actual) { - assertObjectEquals(expected.toPlainObject(), actual.toPlainObject()); -} - - -/** - * @return {!fireauth.storage.IndexedDB} The default indexedDB - * local storage manager to be used for testing. - */ -function getDefaultFireauthManager() { - return new fireauth.storage.IndexedDB( - 'firebaseLocalStorageDb', - 'firebaseLocalStorage', - 'fbase_key', - 'value', - 1, - indexedDBMock); -} - - -function testIndexedDb_notSupported() { - // Test when indexedDB is not supported. - stubs.replace( - fireauth.storage.IndexedDB, - 'isAvailable', - function() { - return false; - }); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED); - try { - getDefaultFireauthManager(); - fail('Should fail when indexedDB is not supported.'); - } catch (error) { - assertErrorEquals(expectedError, error); - } -} - - -function testIndexedDb_null() { - manager = getDefaultFireauthManager(); - assertEquals(fireauth.storage.Storage.Type.INDEXEDDB, manager.type); - return manager.get('key1') - .then(function(data) { - assertNull(data); - }); -} - - -function testIndexedDb_setGetRemove_objectStoreContained() { - manager = getDefaultFireauthManager(); - manager.addStorageListener(function() { - fail('Storage should not be triggered for local changes!'); - }); - return goog.Promise.resolve() - .then(function() { - return manager.set('key1', 'value1'); - }) - .then(function() { - // No database deletion should occur. - assertEquals(0, deleted); - return manager.get('key1'); - }) - .then(function(data) { - assertEquals('value1', data); - }) - .then(function() { - return manager.remove('key1'); - }) - .then(function() { - return manager.get('key1'); - }) - .then(function(data) { - assertNull(data); - }); -} - - -function testIndexedDb_setGetRemove_objectStoreMissing() { - // Track number of initialization trials. - var trials = 2; - containsObjectStore = function() { - trials--; - // Fail first time and then succeed the second time. - if (trials != 0) { - return false; - } - return true; - }; - manager = getDefaultFireauthManager(); - manager.addStorageListener(function() { - fail('Storage should not be triggered for local changes!'); - }); - return goog.Promise.resolve() - .then(function() { - return manager.set('key1', 'value1'); - }) - .then(function() { - // Db should have been deleted and re-initialized. - assertEquals(0, trials); - assertEquals(1, deleted); - return manager.get('key1'); - }) - .then(function(data) { - assertEquals('value1', data); - }) - .then(function() { - return manager.remove('key1'); - }) - .then(function() { - return manager.get('key1'); - }) - .then(function(data) { - assertNull(data); - }); -} - - -function testIndexedDb_setGetRemove_connectionClosed() { - manager = getDefaultFireauthManager(); - manager.addStorageListener(() => { - fail('Storage should not be triggered for local changes!'); - }); - let numAttempts = 0; - let errorThrown = false; - return goog.Promise.resolve() - .then(() => { - return manager.get('key1'); - }) - .then(() => { - // Close the connection and try a set, it should not throw. - db.close(); - return manager.set('key1', 'value1'); - }) - .then(() => { - // Close the connection and try a get, it should not throw. - db.close(); - return manager.get('key1'); - }) - .then((data) => { - // Close the connection and try a remove, it should not throw. - db.close(); - return manager.remove('key1'); - }) - .then(() => { - db.close(); - indexedDBMock.onopen_ = (db) => { - numAttempts++; - db.close(); - }; - return manager.set('key1', 'value1'); - }) - .thenCatch((error) => { - assertEquals( - error.message, - 'Failed to execute \'transaction\' on \'IDBDatabase\'.'); - errorThrown = true; - }) - .then(() => { - assertEquals(3, numAttempts); - assertTrue(errorThrown); - }); -} - - -function testIndexedDb_failingOnDbOpen() { - manager = getDefaultFireauthManager(); - manager.addStorageListener(() => { - fail('Storage should not be triggered for local changes!'); - }); - let errorThrown = false; - indexedDBMock.open = () => { - throw new Error('InvalidStateError: A mutation operation was attempted ' + - 'on a database that did not allow mutations.'); - }; - return goog.Promise.resolve() - .then(() => { - return manager.get('key1'); - }) - .thenCatch((error) => { - assertEquals( - error.message, - 'InvalidStateError: A mutation operation was attempted on a ' + - 'database that did not allow mutations.'); - errorThrown = true; - }) - .then(() => { - assertTrue(errorThrown); - }); -} - - -function testStartListeners() { - manager = getDefaultFireauthManager(); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - - // Add listeners. - manager.addStorageListener(listener1); - manager.addStorageListener(listener2); - clock.tick(800); - clock.tick(800); - clock.tick(800); - db.store['firebaseLocalStorage'] = { - 'key1': {'fbase_key': 'key1', 'value': 1}, - 'key2': {'fbase_key': 'key2', 'value': 2} - }; - clock.tick(800); - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener2.getCallCount()); - assertArrayEquals( - ['key1', 'key2'], listener1.getLastCall().getArgument(0)); - assertArrayEquals( - ['key1', 'key2'], listener2.getLastCall().getArgument(0)); -} - - -function testStopListeners() { - manager = getDefaultFireauthManager(); - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - // Add listeners. - manager.addStorageListener(listener1); - manager.addStorageListener(listener2); - manager.addStorageListener(listener3); - clock.tick(800); - clock.tick(800); - clock.tick(800); - // Remove all but listener3. - manager.removeStorageListener(listener1); - manager.removeStorageListener(listener2); - db.store['firebaseLocalStorage'] = { - 'key1': {'fbase_key': 'key1', 'value': 1}, - 'key2': {'fbase_key': 'key2', 'value': 2} - }; - clock.tick(800); - // Only listener3 should be called. - assertEquals(0, listener1.getCallCount()); - assertEquals(0, listener2.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertArrayEquals( - ['key1', 'key2'], listener3.getLastCall().getArgument(0)); - db.store['firebaseLocalStorage'] = { - 'key1': {'fbase_key': 'key1', 'value': 1}, - 'key3': {'fbase_key': 'key3', 'value': 3} - }; - clock.tick(800); - // Only listener3 should be called. - assertEquals(0, listener1.getCallCount()); - assertEquals(0, listener2.getCallCount()); - assertEquals(2, listener3.getCallCount()); - assertArrayEquals( - ['key2', 'key3'], listener3.getLastCall().getArgument(0)); - // Remove listener3. - manager.removeStorageListener(listener3); - // Add listener1 again. - manager.addStorageListener(listener1); - db.store['firebaseLocalStorage'] = { - 'key1': {'fbase_key': 'key1', 'value': 0}, - 'key3': {'fbase_key': 'key3', 'value': 3}, - 'key4': {'fbase_key': 'key4', 'value': 4} - }; - clock.tick(800); - // Only listener1 should be called. - assertEquals(1, listener1.getCallCount()); - assertEquals(0, listener2.getCallCount()); - assertEquals(2, listener3.getCallCount()); - assertArrayEquals( - ['key1', 'key4'], listener1.getLastCall().getArgument(0)); - // Remove all listeners. - manager.removeAllStorageListeners(); - db.store['firebaseLocalStorage'] = { - 'key1': {'fbase_key': 'key1', 'value': 0} - }; - clock.tick(800); - // No additional listener should be called. - assertEquals(1, listener1.getCallCount()); - assertEquals(0, listener2.getCallCount()); - assertEquals(2, listener3.getCallCount()); -} - - -function testReceiverSubscribed_noWorkerGlobalScope() { - var getWorkerGlobalScope = mockControl.createMethodMock( - fireauth.util, 'getWorkerGlobalScope'); - var getInstance = mockControl.createMethodMock( - fireauth.messagechannel.Receiver, 'getInstance'); - getWorkerGlobalScope().$returns(null).$atLeastOnce(); - getInstance().$never(); - mockControl.$replayAll(); - - manager = getDefaultFireauthManager(); - return manager.get('abc').then(function(value) { - assertNull(value); - }); -} - - -function testReceiverSubscribed_externalChange_notYetProcessed() { - var listener1 = goog.testing.recordFunction(); - var subscribedCallback; - var pingCallback; - var workerGlobalScope = {}; - var getWorkerGlobalScope = mockControl.createMethodMock( - fireauth.util, 'getWorkerGlobalScope'); - var receiver = mockControl.createStrictMock(fireauth.messagechannel.Receiver); - var getInstance = mockControl.createMethodMock( - fireauth.messagechannel.Receiver, 'getInstance'); - getWorkerGlobalScope().$returns(workerGlobalScope).$atLeastOnce(); - getInstance(workerGlobalScope).$returns(receiver); - receiver.subscribe('keyChanged', ignoreArgument) - .$does(function(eventType, callback) { - subscribedCallback = callback; - }).$once(); - receiver.subscribe('ping', ignoreArgument) - .$does(function(eventType, callback) { - pingCallback = callback; - }).$once(); - mockControl.$replayAll(); - - manager = getDefaultFireauthManager(); - manager.addStorageListener(listener1); - return manager.get('abc').then(function(value) { - // Simulate ping response at this point. - pingCallback('https://www.example.com', {}); - assertNull(value); - // Simulate external indexedDB change. - db.store['firebaseLocalStorage'] = { - 'abc': {'fbase_key': 'abc', 'value': 'def'} - }; - return subscribedCallback('https://www.example.com', {'key': 'abc'}); - }).then(function(response) { - // Confirm sync completed. Status true returned to confirm the key was - // processed. - assertObjectEquals({'keyProcessed': true}, response); - // Listener should trigger with expected argument. - assertEquals(1, listener1.getCallCount()); - assertArrayEquals(['abc'], listener1.getLastCall().getArgument(0)); - return manager.get('abc'); - }).then(function(value) { - assertEquals('def', value); - }); -} - - -function testReceiverSubscribed_externalChange_notYetProcessed_pingTimeout() { - // Confirm ping timeout does not affect the flow. This is mostly used for - // optimization for slow browsers. - var listener1 = goog.testing.recordFunction(); - var subscribedCallback; - var workerGlobalScope = {}; - var getWorkerGlobalScope = mockControl.createMethodMock( - fireauth.util, 'getWorkerGlobalScope'); - var receiver = mockControl.createStrictMock(fireauth.messagechannel.Receiver); - var getInstance = mockControl.createMethodMock( - fireauth.messagechannel.Receiver, 'getInstance'); - getWorkerGlobalScope().$returns(workerGlobalScope).$atLeastOnce(); - getInstance(workerGlobalScope).$returns(receiver); - receiver.subscribe('keyChanged', ignoreArgument) - .$does(function(eventType, callback) { - subscribedCallback = callback; - }).$once(); - // Simulate no response to ping. This means short timeout will be used by - // sender. - receiver.subscribe('ping', ignoreArgument).$once(); - mockControl.$replayAll(); - - manager = getDefaultFireauthManager(); - manager.addStorageListener(listener1); - return manager.get('abc').then(function(value) { - assertNull(value); - // Simulate external indexedDB change. - db.store['firebaseLocalStorage'] = { - 'abc': {'fbase_key': 'abc', 'value': 'def'} - }; - return subscribedCallback('https://www.example.com', {'key': 'abc'}); - }).then(function(response) { - // Confirm sync completed. Status true returned to confirm the key was - // processed. - assertObjectEquals({'keyProcessed': true}, response); - // Listener should trigger with expected argument. - assertEquals(1, listener1.getCallCount()); - assertArrayEquals(['abc'], listener1.getLastCall().getArgument(0)); - return manager.get('abc'); - }).then(function(value) { - assertEquals('def', value); - }); -} - - -function testReceiverSubscribed_externalChange_alreadyProcessed() { - var listener1 = goog.testing.recordFunction(); - var subscribedCallback; - var pingCallback; - var workerGlobalScope = {}; - var getWorkerGlobalScope = mockControl.createMethodMock( - fireauth.util, 'getWorkerGlobalScope'); - var receiver = mockControl.createStrictMock(fireauth.messagechannel.Receiver); - var getInstance = mockControl.createMethodMock( - fireauth.messagechannel.Receiver, 'getInstance'); - getWorkerGlobalScope().$returns(workerGlobalScope).$atLeastOnce(); - getInstance(workerGlobalScope).$returns(receiver); - receiver.subscribe('keyChanged', ignoreArgument) - .$does(function(eventType, callback) { - subscribedCallback = callback; - }).$once(); - receiver.subscribe('ping', ignoreArgument) - .$does(function(eventType, callback) { - pingCallback = callback; - }).$once(); - mockControl.$replayAll(); - - manager = getDefaultFireauthManager(); - manager.addStorageListener(listener1); - return manager.set('abc', 'def').then(function() { - // Simulate ping response at this point. - pingCallback('https://www.example.com', {}); - return subscribedCallback('https://www.example.com', {'key': 'abc'}); - }).then(function(response) { - // Confirm sync completed but key not processed since no change is detected. - assertObjectEquals({'keyProcessed': false}, response); - // No listener should trigger. - assertEquals(0, listener1.getCallCount()); - return manager.get('abc'); - }).then(function(value) { - assertEquals('def', value); - }); -} - - -function testSender_writeOperations_serviceWorkerControllerUnavailable() { - var getActiveServiceWorker = mockControl.createMethodMock( - fireauth.util, 'getActiveServiceWorker'); - var sender = mockControl.createStrictMock(fireauth.messagechannel.Sender); - var senderConstructor = mockControl.createConstructorMock( - fireauth.messagechannel, 'Sender'); - getActiveServiceWorker() - .$returns(goog.Promise.resolve(null)).$atLeastOnce(); - // No sender initialized. - senderConstructor(ignoreArgument).$never(); - sender.send(ignoreArgument).$never(); - mockControl.$replayAll(); - - // Set and remove should not trigger sender. - manager = getDefaultFireauthManager(); - return manager.set('abc', 'def').then(function() { - return manager.get('abc'); - }).then(function(value) { - assertEquals('def', value); - return manager.remove('abc'); - }).then(function() { - return manager.get('abc'); - }).then(function(value) { - assertNull(value); - }); -} - - -function testSender_writeOperations_serviceWorkerControllerAvailable() { - var status = []; - var serviceWorkerController = {}; - var getServiceWorkerController = mockControl.createMethodMock( - fireauth.util, 'getServiceWorkerController'); - var getActiveServiceWorker = mockControl.createMethodMock( - fireauth.util, 'getActiveServiceWorker'); - var sender = mockControl.createStrictMock(fireauth.messagechannel.Sender); - var senderConstructor = mockControl.createConstructorMock( - fireauth.messagechannel, 'Sender'); - var resolvePing; - getActiveServiceWorker() - .$returns(goog.Promise.resolve(serviceWorkerController)).$atLeastOnce(); - // Successful set event. - senderConstructor(ignoreArgument).$returns(sender); - sender.send('ping', null, true).$does(function(eventType, data) { - return new goog.Promise(function(resolve, reject) { - resolvePing = resolve; - }); - }).$once(); - // Short timeout used as ping response not yet received. - getServiceWorkerController().$returns(serviceWorkerController).$once(); - sender.send('keyChanged', {'key': 'abc'}, false) - .$does(function(eventType, data) { - status.push(0); - return goog.Promise.resolve([ - { - 'fulfilled': true, - 'value': {'keyProcessed': true} - } - ]); - }).$once(); - // Successful remove event. - // Short timeout used as ping response not yet received. - getServiceWorkerController().$returns(serviceWorkerController).$once(); - sender.send('keyChanged', {'key': 'abc'}, false) - .$does(function(eventType, data) { - // Simulate ping resolved at this point. - resolvePing([ - { - 'fulfilled': true, - 'value': ['keyChanged'] - } - ]); - status.push(1); - return goog.Promise.resolve([ - { - 'fulfilled': true, - 'value': {'keyProcessed': true} - } - ]); - }).$once(); - // Long timeout used as ping response has been resolved. - // Failing set event. - getServiceWorkerController().$returns(serviceWorkerController).$once(); - sender.send('keyChanged', {'key': 'abc'}, true) - .$does(function(eventType, data) { - status.push(2); - return goog.Promise.reject(new Error('unsupported_event')); - }).$once(); - // Failing remove event. - getServiceWorkerController().$returns(serviceWorkerController).$once(); - sender.send('keyChanged', {'key': 'abc'}, true) - .$does(function(eventType, data) { - status.push(3); - return goog.Promise.reject(new Error('invalid_response')); - }).$once(); - // Simulate the active service worker changed for some reason. No send - // requests sent anymore. - getServiceWorkerController().$returns({}).$once(); - mockControl.$replayAll(); - - manager = getDefaultFireauthManager(); - return manager.set('abc', 'def').then(function() { - // Set should trigger sender at this point. - assertArrayEquals([0], status); - return manager.get('abc'); - }).then(function(value) { - // Get shouldn't trigger. - assertArrayEquals([0], status); - assertEquals('def', value); - return manager.remove('abc'); - }).then(function() { - // Remove should trigger sender at this point. - assertArrayEquals([0, 1], status); - return manager.get('abc'); - }).then(function(value) { - // Get shouldn't trigger. - assertArrayEquals([0, 1], status); - assertNull(value); - // This should resolve even if sender error occurs. - return manager.set('abc', 'ghi'); - }).then(function() { - // Set should trigger sender at this point. - assertArrayEquals([0, 1, 2], status); - return manager.get('abc'); - }).then(function(value) { - // Get shouldn't trigger. - assertArrayEquals([0, 1, 2], status); - assertEquals('ghi', value); - // This should resolve even if sender error occurs. - return manager.remove('abc'); - }).then(function() { - // Remove should trigger sender at this point. - assertArrayEquals([0, 1, 2, 3], status); - return manager.get('abc'); - }).then(function(value) { - // Get shouldn't trigger. - assertArrayEquals([0, 1, 2, 3], status); - assertNull(value); - return manager.set('abc', 'ghi'); - }).then(function() { - // This shouldn't trigger sender anymore since active service worker - // changed. - assertArrayEquals([0, 1, 2, 3], status); - return manager.get('abc'); - }).then(function(value) { - assertEquals('ghi', value); - }); -} diff --git a/packages/auth/test/storage/inmemorystorage_test.js b/packages/auth/test/storage/inmemorystorage_test.js deleted file mode 100644 index 26c171189c5..00000000000 --- a/packages/auth/test/storage/inmemorystorage_test.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.InMemoryStorageTest'); - -goog.require('fireauth.storage.InMemoryStorage'); -goog.require('fireauth.storage.Storage'); -/** @suppress {extraRequire} */ -goog.require('fireauth.storage.testHelper'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.storage.InMemoryStorageTest'); - - -var storage; - - -function setUp() { - storage = new fireauth.storage.InMemoryStorage(); -} - - -function tearDown() { - storage = null; -} - - -function testBasicStorageOperations() { - assertEquals(fireauth.storage.Storage.Type.IN_MEMORY, storage.type); - return assertBasicStorageOperations(storage); -} - - -function testDifferentTypes() { - return assertDifferentTypes(storage); -} diff --git a/packages/auth/test/storage/localstorage_test.js b/packages/auth/test/storage/localstorage_test.js deleted file mode 100644 index 2da9e3a0cf1..00000000000 --- a/packages/auth/test/storage/localstorage_test.js +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.LocalStorageTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.storage.LocalStorage'); -goog.require('fireauth.storage.Storage'); -/** @suppress {extraRequire} */ -goog.require('fireauth.storage.testHelper'); -goog.require('fireauth.util'); -goog.require('goog.events.EventType'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.events'); -goog.require('goog.testing.events.Event'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.storage.LocalStorageTest'); - - -var stubs = new goog.testing.PropertyReplacer(); -var storage; - - -function setUp() { - storage = new fireauth.storage.LocalStorage(); -} - - -function tearDown() { - storage = null; - stubs.reset(); - localStorage.clear(); -} - - -/** Simulates a Node.js environment. */ -function simulateNodeEnvironment() { - // Node.js environment. - stubs.replace( - fireauth.util, - 'getEnvironment', - function() {return fireauth.util.Env.NODE;}); - // No window.localStorage. - stubs.replace( - fireauth.storage.LocalStorage, - 'getGlobalStorage', - function() {return null;}); -} - - -function testBasicStorageOperations() { - assertEquals(fireauth.storage.Storage.Type.LOCAL_STORAGE, storage.type); - return assertBasicStorageOperations(storage); -} - - -function testDifferentTypes() { - return assertDifferentTypes(storage); -} - - -function testListeners() { - var storageEvent; - - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - - storage.addStorageListener(listener1); - storage.addStorageListener(listener3); - - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'myKey'; - goog.testing.events.fireBrowserEvent(storageEvent); - - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - - storage.removeStorageListener(listener3); - - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'myKey2'; - goog.testing.events.fireBrowserEvent(storageEvent); - - assertEquals(2, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); -} - - -function testNotAvailable() { - stubs.replace( - fireauth.storage.LocalStorage, 'isAvailable', - function() { return false; }); - var error = assertThrows(function() { new fireauth.storage.LocalStorage(); }); - assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED), - error); -} - - -function testBasicStorageOperations_node() { - simulateNodeEnvironment(); - storage = new fireauth.storage.LocalStorage(); - return assertBasicStorageOperations(storage); -} - - -function testDifferentTypes_node() { - simulateNodeEnvironment(); - storage = new fireauth.storage.LocalStorage(); - return assertDifferentTypes(storage); -} - - -function testNotAvailable_node() { - // Compatibility libraries not included. - stubs.replace(firebase.INTERNAL, 'node', {}); - // Simulate Node.js environment. - simulateNodeEnvironment(); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'The LocalStorage compatibility library was not found.'); - var error = assertThrows(function() { new fireauth.storage.LocalStorage(); }); - assertErrorEquals( - expectedError, - error); -} diff --git a/packages/auth/test/storage/mockstorage_test.js b/packages/auth/test/storage/mockstorage_test.js deleted file mode 100644 index b6fb6beaa3c..00000000000 --- a/packages/auth/test/storage/mockstorage_test.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.MockStorageTest'); - -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.Storage'); -/** @suppress {extraRequire} */ -goog.require('fireauth.storage.testHelper'); -goog.require('goog.events.EventType'); -goog.require('goog.testing.events'); -goog.require('goog.testing.events.Event'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.storage.MockStorageTest'); - - -var storage; - - -function setUp() { - storage = new fireauth.storage.MockStorage(); -} - - -function tearDown() { - storage = null; -} - - -function testBasicStorageOperations() { - assertEquals(fireauth.storage.Storage.Type.MOCK_STORAGE, storage.type); - return assertBasicStorageOperations(storage); -} - - -function testDifferentTypes() { - return assertDifferentTypes(storage); -} - - -function testListeners() { - var storageEvent; - - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var listener3 = goog.testing.recordFunction(); - - storage.addStorageListener(listener1); - storage.addStorageListener(listener3); - - storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'myKey1'; - storageEvent.newValue = JSON.stringify('value1'); - storage.fireBrowserEvent(storageEvent); - - assertEquals(1, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - return storage.get('myKey1').then(function(value) { - assertEquals('value1', value); - - storage.removeStorageListener(listener3); - - storageEvent.key = 'myKey2'; - storageEvent.newValue = JSON.stringify('value2'); - storage.fireBrowserEvent(storageEvent); - - assertEquals(2, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - return storage.get('myKey2'); - }).then(function(value) { - assertEquals('value2', value); - storageEvent.key = 'myKey1'; - storageEvent.newValue = null; - storage.fireBrowserEvent(storageEvent); - - assertEquals(3, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - - return storage.get('myKey1'); - }).then(function(value) { - assertUndefined(value); - - storage.removeStorageListener(listener1); - - storageEvent.key = null; - storage.fireBrowserEvent(storageEvent); - - assertEquals(3, listener1.getCallCount()); - assertEquals(1, listener3.getCallCount()); - assertEquals(0, listener2.getCallCount()); - - return storage.get('myKey2'); - }).then(function(value) { - assertUndefined(value); - }); -} - - -function testClear() { - storage.set('myKey1', 'value1'); - storage.set('myKey2', 'value2'); - storage.clear(); - return storage.get('myKey1').then(function(value) { - assertUndefined(value); - return storage.get('myKey2'); - }).then(function(value) { - assertUndefined(value); - }); -} diff --git a/packages/auth/test/storage/nullstorage_test.js b/packages/auth/test/storage/nullstorage_test.js deleted file mode 100644 index a878a262a5f..00000000000 --- a/packages/auth/test/storage/nullstorage_test.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.NullStorageTest'); - -goog.require('fireauth.storage.NullStorage'); -goog.require('fireauth.storage.Storage'); -goog.require('goog.Promise'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.storage.NullStorageTest'); - - - -function testNullStorage() { - var storage = new fireauth.storage.NullStorage(); - var listener = function() {}; - assertEquals(fireauth.storage.Storage.Type.NULL_STORAGE, storage.type); - storage.addStorageListener(listener); - storage.removeStorageListener(listener); - return goog.Promise.resolve() - .then(function() { return storage.set('foo', 'bar'); }) - .then(function() { return storage.get('foo'); }) - .then(function(value) { - assertNull(value); - return storage.remove('foo'); - }); -} diff --git a/packages/auth/test/storage/sessionstorage_test.js b/packages/auth/test/storage/sessionstorage_test.js deleted file mode 100644 index b5d6d621d7d..00000000000 --- a/packages/auth/test/storage/sessionstorage_test.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -goog.provide('fireauth.storage.SessionStorageTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.storage.SessionStorage'); -goog.require('fireauth.storage.Storage'); -/** @suppress {extraRequire} */ -goog.require('fireauth.storage.testHelper'); -goog.require('fireauth.util'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.storage.SessionStorageTest'); - - -var stubs = new goog.testing.PropertyReplacer(); -var storage; - - -function setUp() { - storage = new fireauth.storage.SessionStorage(); -} - - -function tearDown() { - storage = null; - stubs.reset(); - sessionStorage.clear(); -} - - -/** Simulates a Node.js environment. */ -function simulateNodeEnvironment() { - // Node.js environment. - stubs.replace( - fireauth.util, - 'getEnvironment', - function() {return fireauth.util.Env.NODE;}); - // No window.sessionStorage. - stubs.replace( - fireauth.storage.SessionStorage, - 'getGlobalStorage', - function() {return null;}); -} - - -function testBasicStorageOperations() { - assertEquals(fireauth.storage.Storage.Type.SESSION_STORAGE, storage.type); - return assertBasicStorageOperations(storage); -} - - -function testDifferentTypes() { - return assertDifferentTypes(storage); -} - - -function testNotAvailable() { - stubs.replace( - fireauth.storage.SessionStorage, 'isAvailable', - function() { return false; }); - var error = assertThrows(function() { - new fireauth.storage.SessionStorage(); - }); - assertErrorEquals( - new fireauth.AuthError(fireauth.authenum.Error.WEB_STORAGE_UNSUPPORTED), - error); -} - - -function testBasicStorageOperations_node() { - simulateNodeEnvironment(); - storage = new fireauth.storage.SessionStorage(); - return assertBasicStorageOperations(storage); -} - - -function testDifferentTypes_node() { - simulateNodeEnvironment(); - storage = new fireauth.storage.SessionStorage(); - return assertDifferentTypes(storage); -} - - -function testNotAvailable_node() { - // Compatibility libraries not included. - stubs.replace(firebase.INTERNAL, 'node', {}); - // Simulate Node.js environment. - simulateNodeEnvironment(); - var expectedError = new fireauth.AuthError( - fireauth.authenum.Error.INTERNAL_ERROR, - 'The SessionStorage compatibility library was not found.'); - var error = assertThrows(function() { - new fireauth.storage.SessionStorage(); - }); - assertErrorEquals( - expectedError, - error); -} diff --git a/packages/auth/test/storage/testhelper.js b/packages/auth/test/storage/testhelper.js deleted file mode 100644 index e58d0ac056c..00000000000 --- a/packages/auth/test/storage/testhelper.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Provides utilities for testing storage. - */ -goog.provide('fireauth.storage.testHelper'); -goog.provide('fireauth.storage.testHelper.FakeAsyncStorage'); -goog.setTestOnly('fireauth.storage.testHelper'); - -goog.require('goog.Promise'); - - -/** - * Provides a fake implementation of the React Native AsyncStorage API. It - * currently does not implement all of the APIs. - * @constructor - * @see https://facebook.github.io/react-native/docs/asyncstorage.html - */ -fireauth.storage.testHelper.FakeAsyncStorage = function() { - /** @private {!Object} */ - this.storage_ = {}; -}; - - -/** - * @param {string} key - * @return {!goog.Promise} - */ -fireauth.storage.testHelper.FakeAsyncStorage.prototype.getItem = - function(key) { - return goog.Promise.resolve(this.storage_[key]); -}; - - -/** - * @param {string} key - * @param {string} value - * @return {!goog.Promise} - */ -fireauth.storage.testHelper.FakeAsyncStorage.prototype.setItem = - function(key, value) { - this.storage_[key] = value; - return goog.Promise.resolve(); -}; - - -/** - * @param {string} key - * @return {!goog.Promise} - */ -fireauth.storage.testHelper.FakeAsyncStorage.prototype.removeItem = - function(key) { - delete this.storage_[key]; - return goog.Promise.resolve(); -}; - - -var storageFirebaseExtension = { - 'INTERNAL': { - 'reactNative': { - 'AsyncStorage': new fireauth.storage.testHelper.FakeAsyncStorage() - }, - 'node': { - 'localStorage': window.localStorage, - 'sessionStorage': window.sessionStorage, - } - } -}; - - -if (!goog.global['firebase'] || !goog.global['firebase']['INTERNAL']) { - goog.global['firebase'] = storageFirebaseExtension; -} else { - goog.global['firebase']['INTERNAL']['extendNamespace']( - storageFirebaseExtension); -} - - -/** - * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be - * used as Internet Explorer adds the stack trace as a property of the object. - * @param {!fireauth.AuthError} expected - * @param {!fireauth.AuthError} actual - */ -function assertErrorEquals(expected, actual) { - assertObjectEquals(expected.toPlainObject(), actual.toPlainObject()); -} - - -/** - * @param {!fireauth.storage.Storage} storage - * @return {!goog.Promise} - */ -function assertBasicStorageOperations(storage) { - return goog.Promise.resolve() - .then(function() { return storage.get('foo'); }) - .then(function(value) { - assertUndefined(value); - return storage.set('foo', 'bar'); - }) - .then(function() { return storage.get('foo'); }) - .then(function(value) { - assertEquals('bar', value); - return storage.remove('foo'); - }) - .then(function() { return storage.get('foo'); }) - .then(function(value) { assertUndefined(value); }); -} - - -/** - * @param {!fireauth.storage.Storage} storage - * @return {!goog.Promise} - */ -function assertDifferentTypes(storage) { - var obj = {'a': 1.2, 'b': 'foo'}; - var num = 54; - var bool = true; - return goog.Promise.resolve() - .then(function() { - return goog.Promise.all([ - storage.set('obj', obj), storage.set('num', num), - storage.set('bool', bool), storage.set('null', null) - ]); - }) - .then(function() { - return goog.Promise.all([ - storage.get('obj'), storage.get('num'), storage.get('bool'), - storage.get('null'), storage.get('undefined') - ]); - }) - .then(function(values) { - assertObjectEquals(obj, values[0]); - assertEquals(num, values[1]); - assertEquals(bool, values[2]); - assertNull(values[3]); - }); -} diff --git a/packages/auth/test/storageautheventmanager_test.js b/packages/auth/test/storageautheventmanager_test.js deleted file mode 100644 index 29dd9b420fd..00000000000 --- a/packages/auth/test/storageautheventmanager_test.js +++ /dev/null @@ -1,203 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for storageautheventmanager.js - */ - -goog.provide('fireauth.storage.AuthEventManagerTest'); - -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.authStorage'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.storage.AuthEventManager'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.events'); -goog.require('goog.testing.events.Event'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.storage.AuthEventManagerTest'); - - -var appId = 'appId1'; -var stubs = new goog.testing.PropertyReplacer(); -var mockLocalStorage; -var mockSessionStorage; - - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); - // Simulate browser that synchronizes between and iframe and a popup. - stubs.replace( - fireauth.util, - 'isLocalStorageNotSynchronized', - function() { - return false; - }); - window.localStorage.clear(); - window.sessionStorage.clear(); -} - - -function tearDown() { - stubs.reset(); -} - - -/** - * @return {!fireauth.authStorage.Manager} The default local storage - * synchronized manager instance used for testing. - */ -function getDefaultStorageManagerInstance() { - return new fireauth.authStorage.Manager('firebase', ':', false, true); -} - - -function testGetSetRemoveAuthEvent() { - var storageManager = getDefaultStorageManagerInstance(); - var authEventManager = - new fireauth.storage.AuthEventManager(appId, storageManager); - var expectedAuthEvent = new fireauth.AuthEvent( - 'signInViaPopup', - '1234', - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'); - var storageKey = 'firebase:authEvent:appId1'; - return goog.Promise.resolve() - .then(function() { - // Set expected Auth event in localStorage. - return mockLocalStorage.set( - storageKey, expectedAuthEvent.toPlainObject()); - }) - .then(function() { - return authEventManager.getAuthEvent(); - }) - .then(function(authEvent) { - assertObjectEquals(expectedAuthEvent, authEvent); - }) - .then(function() { - return authEventManager.removeAuthEvent(); - }) - .then(function() { - return mockLocalStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return authEventManager.getAuthEvent(); - }) - .then(function(authEvent) { - assertNull(authEvent); - }); -} - - -function testGetSetRemoveRedirectEvent() { - var storageManager = getDefaultStorageManagerInstance(); - var authEventManager = - new fireauth.storage.AuthEventManager(appId, storageManager); - var expectedAuthEvent = new fireauth.AuthEvent( - 'signInViaRedirect', - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'); - // Set expected Auth event in sessionStorage. - mockSessionStorage.set( - 'firebase:redirectEvent:appId1', - expectedAuthEvent.toPlainObject()); - var storageKey = 'firebase:redirectEvent:appId1'; - return goog.Promise.resolve() - .then(function() { - return authEventManager.getRedirectEvent(); - }) - .then(function(authEvent) { - assertObjectEquals(expectedAuthEvent, authEvent); - }) - .then(function() { - return authEventManager.removeRedirectEvent(); - }) - .then(function() { - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return authEventManager.getRedirectEvent(); - }) - .then(function(authEvent) { - assertNull(authEvent); - }); -} - - -function testAddRemoveAuthEventListener() { - var expectedAuthEvent = new fireauth.AuthEvent( - 'signInViaRedirect', - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'); - var storageManager = getDefaultStorageManagerInstance(); - var authEventManager = - new fireauth.storage.AuthEventManager('appId1', storageManager); - var listener = goog.testing.recordFunction(); - // Save existing Auth events for appId1 and appId2. - return goog.Promise.resolve() - .then(function() { - return mockLocalStorage.set( - 'firebase:authEvent:appId1', expectedAuthEvent.toPlainObject()); - }) - .then(function() { - // Set expected Auth event in localStorage. - return mockLocalStorage.set( - 'firebase:authEvent:appId2', expectedAuthEvent.toPlainObject()); - }) - .then(function() { - authEventManager.addAuthEventListener(listener); - // Simulate appId1 event deletion. - storageEvent = new goog.testing.events.Event( - goog.events.EventType.STORAGE, window); - storageEvent.key = 'firebase:authEvent:appId1'; - storageEvent.newValue = null; - // This should trigger listener. - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, listener.getCallCount()); - // Simulate appId2 event deletion. - storageEvent.key = 'firebase:authEvent:appId2'; - // This should not trigger listener. - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, listener.getCallCount()); - // Remove listener. - authEventManager.removeAuthEventListener(listener); - // Simulate new event saved for appId1. - // This should not trigger listener anymore. - storageEvent.key = 'firebase:authEvent:appId1'; - storageEvent.oldValue = null; - storageEvent.newValue = - JSON.stringify(expectedAuthEvent.toPlainObject()); - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, listener.getCallCount()); - }); -} diff --git a/packages/auth/test/storageoauthhandlermanager_test.js b/packages/auth/test/storageoauthhandlermanager_test.js deleted file mode 100644 index a8710091b60..00000000000 --- a/packages/auth/test/storageoauthhandlermanager_test.js +++ /dev/null @@ -1,192 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for storageoauthhandlermanager.js - */ - -goog.provide('fireauth.storage.OAuthHandlerManagerTest'); - -goog.require('fireauth.AuthEvent'); -goog.require('fireauth.OAuthHelperState'); -goog.require('fireauth.authStorage'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.OAuthHandlerManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.storage.OAuthHandlerManagerTest'); - - -var appId = 'appId1'; -var stubs = new goog.testing.PropertyReplacer(); -var mockLocalStorage; -var mockSessionStorage; - - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); - // Simulate browser that synchronizes between and iframe and a popup. - stubs.replace( - fireauth.util, - 'isLocalStorageNotSynchronized', - function() { - return false; - }); - window.localStorage.clear(); - window.sessionStorage.clear(); -} - - -function tearDown() { - stubs.reset(); -} - - -/** - * @return {!fireauth.authStorage.Manager} The default local storage - * synchronized manager instance used for testing. - */ -function getDefaultStorageManagerInstance() { - return new fireauth.authStorage.Manager('firebase', ':', false, true); -} - - -function testGetSetRemoveSessionId() { - var storageManager = getDefaultStorageManagerInstance(); - var oauthHandlerManager = - new fireauth.storage.OAuthHandlerManager(storageManager); - var expectedSessionId = 'g43g4ngh4hhk4hn042rj290rg4g4'; - var storageKey = 'firebase:sessionId:appId1'; - return goog.Promise.resolve() - .then(function() { - return oauthHandlerManager.setSessionId(appId, expectedSessionId); - }) - .then(function() { - return oauthHandlerManager.getSessionId(appId); - }) - .then(function(sessionId) { - assertObjectEquals(expectedSessionId, sessionId); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertObjectEquals(expectedSessionId, value); - return oauthHandlerManager.removeSessionId(appId); - }) - .then(function() { - return mockSessionStorage.get(storageKey); - }).then(function(value) { - assertUndefined(value); - return oauthHandlerManager.getSessionId(appId); - }) - .then(function(sessionId) { - assertUndefined(sessionId); - }); -} - - -function testSetAuthEvent() { - var storageManager = getDefaultStorageManagerInstance(); - var oauthHandlerManager = - new fireauth.storage.OAuthHandlerManager(storageManager); - var expectedAuthEvent = new fireauth.AuthEvent( - 'signInViaPopup', - '1234', - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'); - return goog.Promise.resolve() - .then(function() { - return oauthHandlerManager.setAuthEvent(appId, expectedAuthEvent); - }) - .then(function() { - return mockLocalStorage.get('firebase:authEvent:appId1'); - }) - .then(function(value) { - assertObjectEquals( - expectedAuthEvent.toPlainObject(), - value); - }); -} - - -function testSetRedirectEvent() { - var storageManager = getDefaultStorageManagerInstance(); - var oauthHandlerManager = - new fireauth.storage.OAuthHandlerManager(storageManager); - var expectedAuthEvent = new fireauth.AuthEvent( - 'signInViaRedirect', - null, - 'http://www.example.com/#oauthResponse', - 'SESSION_ID'); - return goog.Promise.resolve() - .then(function() { - return oauthHandlerManager.setRedirectEvent(appId, expectedAuthEvent); - }) - .then(function() { - return mockSessionStorage.get('firebase:redirectEvent:appId1'); - }).then(function(value) { - assertObjectEquals( - expectedAuthEvent.toPlainObject(), - value); - }); -} - - -function testGetSetRemoveOAuthHelperState() { - var storageManager = getDefaultStorageManagerInstance(); - var oauthHandlerManager = - new fireauth.storage.OAuthHandlerManager(storageManager); - var expectedState = new fireauth.OAuthHelperState( - 'API_KEY', - fireauth.AuthEvent.Type.SIGN_IN_VIA_POPUP, - '12345678', - 'http://www.example.com/redirect'); - var storageKey = 'firebase:oauthHelperState'; - return goog.Promise.resolve() - .then(function() { - return oauthHandlerManager.setOAuthHelperState(expectedState); - }) - .then(function() { - return oauthHandlerManager.getOAuthHelperState(); - }) - .then(function(state) { - assertObjectEquals(expectedState, state); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertObjectEquals(expectedState.toPlainObject(), value); - return oauthHandlerManager.removeOAuthHelperState(); - }) - .then(function() { - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return oauthHandlerManager.getOAuthHelperState(); - }) - .then(function(state) { - assertNull(state); - }); -} diff --git a/packages/auth/test/storagependingredirectmanager_test.js b/packages/auth/test/storagependingredirectmanager_test.js deleted file mode 100644 index 31581025ebc..00000000000 --- a/packages/auth/test/storagependingredirectmanager_test.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for storagependingredirectmanager.js - */ - -goog.provide('fireauth.storage.PendingRedirectManagerTest'); - -goog.require('fireauth.authStorage'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.PendingRedirectManager'); -goog.require('goog.Promise'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.storage.PendingRedirectManagerTest'); - - -var appId = 'appId1'; -var stubs = new goog.testing.PropertyReplacer(); -var mockLocalStorage; -var mockSessionStorage; - - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - window.localStorage.clear(); - window.sessionStorage.clear(); - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); -} - - -/** - * @return {!fireauth.authStorage.Manager} The default local storage - * synchronized manager instance used for testing. - */ -function getDefaultStorageManagerInstance() { - return new fireauth.authStorage.Manager('firebase', ':', false, true); -} - - -function testGetSetPendingStatus() { - var storageManager = getDefaultStorageManagerInstance(); - var pendingRedirectManager = - new fireauth.storage.PendingRedirectManager(appId, storageManager); - var storageKey = 'firebase:pendingRedirect:appId1'; - return goog.Promise.resolve() - .then(function() { - return pendingRedirectManager.setPendingStatus(); - }) - .then(function() { - return pendingRedirectManager.getPendingStatus(); - }) - .then(function(status) { - assertTrue(status); - return mockSessionStorage.get(storageKey); - }).then(function(value) { - assertEquals('pending', value); - return pendingRedirectManager.removePendingStatus(); - }) - .then(function() { - return mockSessionStorage.get(storageKey); - }).then(function(value) { - assertUndefined(value); - return pendingRedirectManager.getPendingStatus(); - }) - .then(function(status) { - assertFalse(status); - }); -} diff --git a/packages/auth/test/storageredirectusermanager_test.js b/packages/auth/test/storageredirectusermanager_test.js deleted file mode 100644 index ea2911f5f44..00000000000 --- a/packages/auth/test/storageredirectusermanager_test.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for storageredirectusermanager.js - */ - -goog.provide('fireauth.storage.RedirectUserManagerTest'); - -goog.require('fireauth.AuthUser'); -goog.require('fireauth.authStorage'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.RedirectUserManager'); -goog.require('goog.Promise'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.storage.RedirectUserManagerTest'); - - -var config = { - apiKey: 'apiKey1' -}; -var appId = 'appId1'; -var clock; -var expectedUser; -var expectedUserWithAuthDomain; -var stubs = new goog.testing.PropertyReplacer(); -var mockLocalStorage; -var mockSessionStorage; -var now = new Date(); - - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - clock = new goog.testing.MockClock(true); - window.localStorage.clear(); - window.sessionStorage.clear(); - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); -} - - -function tearDown() { - if (expectedUser) { - expectedUser.destroy(); - } - if (expectedUserWithAuthDomain) { - expectedUserWithAuthDomain.destroy(); - } - goog.dispose(clock); -} - - -/** - * @return {!fireauth.authStorage.Manager} The default local storage - * synchronized manager instance used for testing. - */ -function getDefaultStorageManagerInstance() { - return new fireauth.authStorage.Manager('firebase', ':', false, true); -} - - -function testGetSetRemoveRedirectUser() { - // Avoid triggering getProjectConfig RPC. - fireauth.AuthEventManager.ENABLED = false; - var storageManager = getDefaultStorageManagerInstance(); - var redirectUserManager = - new fireauth.storage.RedirectUserManager(appId, storageManager); - var config = { - 'apiKey': 'API_KEY', - 'appName': 'appId1' - }; - var configWithAuthDomain = { - 'apiKey': 'API_KEY', - 'appName': 'appId1', - 'authDomain': 'project.firebaseapp.com' - }; - var accountInfo = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'multiFactor': { - 'enrolledFactors': [ - { - 'uid': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrollmentTime': now.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505551234' - }, - { - 'uid': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrollmentTime': now.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505556789' - } - ] - } - }; - var tokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt(), - 'refreshToken': 'refreshToken' - }; - expectedUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Expected user with authDomain. - expectedUserWithAuthDomain = - new fireauth.AuthUser(configWithAuthDomain, tokenResponse, accountInfo); - var storageKey = 'firebase:redirectUser:appId1'; - return goog.Promise.resolve() - .then(function() { - return redirectUserManager.setRedirectUser(expectedUser); - }) - .then(function() { - return redirectUserManager.getRedirectUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(expectedUser, user); - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertObjectEquals(expectedUser.toPlainObject(), value); - // Get user with authDomain. - return redirectUserManager.getRedirectUser('project.firebaseapp.com'); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals( - expectedUserWithAuthDomain, user); - return redirectUserManager.removeRedirectUser(); - }) - .then(function() { - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - assertUndefined(value); - return redirectUserManager.getRedirectUser(); - }) - .then(function(user) { - assertNull(user); - }); -} diff --git a/packages/auth/test/storageusermanager_test.js b/packages/auth/test/storageusermanager_test.js deleted file mode 100644 index 4b52c049c99..00000000000 --- a/packages/auth/test/storageusermanager_test.js +++ /dev/null @@ -1,754 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for storageusermanager.js - */ - -goog.provide('fireauth.storage.UserManagerTest'); - -goog.require('fireauth.AuthUser'); -goog.require('fireauth.authStorage'); -goog.require('fireauth.common.testHelper'); -goog.require('fireauth.constants'); -goog.require('fireauth.storage.MockStorage'); -goog.require('fireauth.storage.UserManager'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.events'); -goog.require('goog.testing.events.Event'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.storage.UserManagerTest'); - - -var config = { - apiKey: 'apiKey1' -}; -var appId = 'appId1'; -var expectedUser; -var expectedUserWithAuthDomain; -var stubs = new goog.testing.PropertyReplacer(); -var testUser; -var testUser2; -var mockLocalStorage; -var mockSessionStorage; -const now = Date.now(); -const nowDate = new Date(now); - - -function setUp() { - // Create new mock storages for persistent and temporary storage before each - // test. - mockLocalStorage = new fireauth.storage.MockStorage(); - mockSessionStorage = new fireauth.storage.MockStorage(); - fireauth.common.testHelper.installMockStorages( - stubs, mockLocalStorage, mockSessionStorage); - // Simulate browser that synchronizes between and iframe and a popup. - stubs.replace( - fireauth.util, - 'isLocalStorageNotSynchronized', - function() { - return false; - }); - stubs.replace(Date, 'now', function() { - return now; - }); - window.localStorage.clear(); - window.sessionStorage.clear(); - var config = { - 'apiKey': 'API_KEY', - 'appName': 'appId1' - }; - var accountInfo = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'multiFactor': { - 'enrolledFactors': [ - { - 'uid': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrollmentTime': nowDate.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505551234' - }, - { - 'uid': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrollmentTime': nowDate.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505556789' - } - ] - } - }; - var accountInfo2 = { - 'uid': 'defaultUserId2', - 'email': 'user2@default.com', - 'displayName': 'defaultDisplayName2', - 'photoURL': 'https://www.default.com/default/default2.png', - 'emailVerified': false - }; - var tokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt(), - 'refreshToken': 'refreshToken', - 'expiresIn': '3600' - }; - testUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - testUser2 = new fireauth.AuthUser(config, tokenResponse, accountInfo2); -} - - -function tearDown() { - if (expectedUser) { - expectedUser.destroy(); - } - if (expectedUserWithAuthDomain) { - expectedUserWithAuthDomain.destroy(); - } - if (testUser) { - testUser.destroy(); - } - if (testUser2) { - testUser2.destroy(); - } -} - - -/** - * @return {!fireauth.authStorage.Manager} The default local storage - * synchronized manager instance used for testing. - */ -function getDefaultStorageManagerInstance() { - return new fireauth.authStorage.Manager('firebase', ':', false, true); -} - - -function testGetSetRemoveCurrentUser() { - // Avoid triggering getProjectConfig RPC. - fireauth.AuthEventManager.ENABLED = false; - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager(appId, storageManager); - var config = { - 'apiKey': 'API_KEY', - 'appName': 'appId1' - }; - var configWithAuthDomain = { - 'apiKey': 'API_KEY', - 'appName': 'appId1', - 'authDomain': 'project.firebaseapp.com' - }; - var accountInfo = { - 'uid': 'defaultUserId', - 'email': 'user@default.com', - 'displayName': 'defaultDisplayName', - 'photoURL': 'https://www.default.com/default/default.png', - 'emailVerified': true, - 'multiFactor': { - 'enrolledFactors': [ - { - 'uid': 'ENROLLMENT_UID1', - 'displayName': 'Work phone number', - 'enrollmentTime': nowDate.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505551234' - }, - { - 'uid': 'ENROLLMENT_UID2', - 'displayName': 'Spouse phone number', - 'enrollmentTime': nowDate.toUTCString(), - 'factorId': fireauth.constants.SecondFactorType.PHONE, - 'phoneNumber': '+16505556789' - } - ] - } - }; - var tokenResponse = { - 'idToken': fireauth.common.testHelper.createMockJwt(), - 'refreshToken': 'refreshToken', - 'expiresIn': '3600' - }; - expectedUser = new fireauth.AuthUser(config, tokenResponse, accountInfo); - // Expected user with authDomain. - expectedUserWithAuthDomain = - new fireauth.AuthUser(configWithAuthDomain, tokenResponse, accountInfo); - // Listen to calls on RPC Handler. - stubs.replace( - fireauth.RpcHandler.prototype, - 'updateEmulatorConfig', - goog.testing.recordFunction( - fireauth.RpcHandler.prototype.updateEmulatorConfig)); - var storageKey = 'firebase:authUser:appId1'; - return goog.Promise.resolve() - .then(function() { - return userManager.setCurrentUser(expectedUser); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - assertObjectEquals(expectedUser.toPlainObject(), user.toPlainObject()); - return mockLocalStorage.get(storageKey); - }) - .then(function(user) { - assertObjectEquals(expectedUser.toPlainObject(), user); - // Get user with authDomain. - return userManager.getCurrentUser('project.firebaseapp.com'); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals( - expectedUserWithAuthDomain, user); - // Get user with authDomain & emulator config. - return userManager.getCurrentUser('project.firebaseapp.com', - { - url: 'http://emulator.test.domain:1234' - }); - }) - .then(function () { - // Verify RpcHandler was notified of config change. - assertEquals(1, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); - assertObjectEquals( - { - url: 'http://emulator.test.domain:1234' - }, - fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() - .getArgument(0)); - return userManager.removeCurrentUser(); - }) - .then(function() { - return mockLocalStorage.get(storageKey); - }) - .then(function(user) { - assertUndefined(user); - return userManager.getCurrentUser(); - }) - .then(function(user) { - assertNull(user); - }); -} - - -function testAddRemoveCurrentUserChangeListener() { - var calls = 0; - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - var listener = function() { - calls++; - if (calls > 1) { - fail('Listener should be called once.'); - } - }; - // Save existing Auth users for appId1 and appId2. - mockLocalStorage.set('firebase:authUser:appId1', testUser.toPlainObject()); - mockLocalStorage.set('firebase:authUser:appId2', testUser.toPlainObject()); - return goog.Promise.resolve().then(function() { - return mockLocalStorage.set( - 'firebase:authUser:appId1', testUser.toPlainObject()); - }) - .then(function() { - return mockLocalStorage.set( - 'firebase:authUser:appId2', testUser.toPlainObject()); - }) - .then(function() { - userManager.addCurrentUserChangeListener(listener); - // Simulate appId1 user deletion. - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = 'firebase:authUser:appId1'; - storageEvent.oldValue = JSON.stringify(testUser.toPlainObject()); - storageEvent.newValue = null; - // This should trigger listener. - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, calls); - // Simulate appId2 user deletion. - storageEvent.key = 'firebase:authUser:appId2'; - storageEvent.oldValue = JSON.stringify(testUser.toPlainObject()); - storageEvent.newValue = null; - // This should not trigger listener. - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, calls); - // Remove listener. - userManager.removeCurrentUserChangeListener(listener); - // Simulate new user saved for appId1. - // This should not trigger listener. - storageEvent.key = 'firebase:authUser:appId1'; - storageEvent.newValue = JSON.stringify(testUser.toPlainObject()); - storageEvent.oldValue = null; - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, calls); - }); -} - - -function testUserManager_initializedWithSession() { - // Save state in session storage. - var storageKey = 'firebase:authUser:appId1'; - mockSessionStorage.set(storageKey, testUser.toPlainObject()); - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - return userManager.getCurrentUser() - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser, user); - // User should be saved in session storage only with everything else - // cleared. - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser, storageManager); - }).then(function() { - // Should be saved in session storage. - return userManager.setCurrentUser(testUser2); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser2, storageManager); - }); -} - - -function testUserManager_initializedWithSession_duplicateStorage() { - // Confirm any duplicate storage is cleared on initialization. - var storageManager = getDefaultStorageManagerInstance(); - var userManager; - // Save state in session storage. - var storageKey = 'firebase:authUser:appId1'; - mockSessionStorage.set(storageKey, testUser.toPlainObject()); - // Add state to other types of storage. - mockLocalStorage.set(storageKey, testUser2.toPlainObject()); - // Set redirect persistence to none. - mockSessionStorage.set( - 'firebase:persistence:appId1', 'none'); - // Save state using in memory storage. - return storageManager.set( - {name: 'authUser', persistent: 'none'}, - testUser.toPlainObject(), - 'appId1') - .then(function() { - userManager = new fireauth.storage.UserManager( - 'appId1', storageManager); - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser, user); - // User should be saved in session storage only with everything else - // cleared. - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser, storageManager); - }).then(function() { - // Should be saved in session storage. - return userManager.setCurrentUser(testUser2); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser2, storageManager); - }); -} - - -function testUserManager_initializedWithInMemory() { - // Save state in in-memory storage. - var storageManager = getDefaultStorageManagerInstance(); - var userManager; - return storageManager.set( - {name: 'authUser', persistent: 'none'}, - testUser.toPlainObject(), - 'appId1') - .then(function() { - userManager = new fireauth.storage.UserManager( - 'appId1', storageManager); - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser, user); - // User should be saved in memory only with everything else - // cleared. - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'none', testUser, storageManager); - }).then(function() { - // Should be saved using in memory storage only. - return userManager.setCurrentUser(testUser2); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'none', testUser2, storageManager); - }); -} - - -function testUserManager_initializedWithLocal() { - // Save state in local storage. - var storageKey = 'firebase:authUser:appId1'; - mockLocalStorage.set(storageKey, testUser.toPlainObject()); - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - return userManager.getCurrentUser() - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser, user); - // User should be saved in local storage only with everything else - // cleared. - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser, storageManager); - }).then(function() { - // Should be saved in local storage only. - return userManager.setCurrentUser(testUser2); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser2, storageManager); - }); -} - - -function testUserManager_initializedWithLocal_migratedFromLocalStorage() { - var storageKey = 'firebase:authUser:appId1'; - // Save Auth state to localStorage. This will be migrated to mockLocalStorage. - window.localStorage.setItem( - storageKey, JSON.stringify(testUser.toPlainObject())); - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - return userManager.getCurrentUser() - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser, user); - // User should be cleared from window.localStorage. - assertNull(window.localStorage.getItem(storageKey)); - // User should be saved in mock local storage only with everything else - // cleared. - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser, storageManager); - }) - .then(function() { - // Should be saved in local storage only. - return userManager.setCurrentUser(testUser2); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser2, storageManager); - }); -} - - -function testUserManager_initializedWithLocal_multiplePersistentStorage() { - var storageKey = 'firebase:authUser:appId1'; - // Save Auth state to window.localStorage. This will be cleared. - window.localStorage.setItem( - storageKey, JSON.stringify(testUser.toPlainObject())); - // Save another Auth state in mockLocalStorage. This will have precedence over - // window.localStorage. - mockLocalStorage.set(storageKey, testUser2.toPlainObject()); - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - return userManager.getCurrentUser() - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - // User should be ignored and cleared from window.localStorage. - assertNull(window.localStorage.getItem(storageKey)); - // Existing user saved in mock local storage persisted with everything - // else cleared. - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser2, storageManager); - }) - .then(function() { - return userManager.setCurrentUser(testUser); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser, storageManager); - }); -} - - -function testUserManager_initializedWithDefault() { - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - return userManager.getCurrentUser() - .then(function(user) { - assertNull(user); - // Should be saved in default local storage. - return userManager.setCurrentUser(testUser2); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser2, storageManager); - }); -} - - -function testUserManager_initializedWithSavedPersistence() { - // Save redirect persistence. - mockSessionStorage.set('firebase:persistence:appId1', 'session'); - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - return userManager.getCurrentUser() - .then(function(user) { - assertNull(user); - // Should be saved in session storage as specified in redirect - // persistence. - return userManager.setCurrentUser(testUser2); - }) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser2, storageManager); - }); -} - - -function testUserManager_savePersistenceForRedirect_default() { - // Confirm savePersistenceForRedirect behavior. - var storageKey = 'firebase:persistence:appId1'; - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - return userManager.savePersistenceForRedirect() - .then(function() { - // Should store persistence value in session storage. - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - // Should apply the current default persistence. - assertEquals('local', value); - }); -} - - -function testUserManager_savePersistenceForRedirect_modifed() { - var storageKey = 'firebase:persistence:appId1'; - var storageManager = getDefaultStorageManagerInstance(); - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - // Update persistence. - userManager.setPersistence('session'); - return userManager.savePersistenceForRedirect() - .then(function() { - // Should store persistence value in session storage. - return mockSessionStorage.get(storageKey); - }) - .then(function(value) { - // The latest modified persistence value should be used. - assertEquals('session', value); - }); -} - - -function testUserManager_clearState_setPersistence() { - // Test setPersistence behavior with initially no saved stated. - var storageManager = getDefaultStorageManagerInstance(); - // As no existing state, the default is local. - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - // Switch to session persistence. - userManager.setPersistence('session'); - // Should be saved in session. - return userManager.setCurrentUser(testUser2) - .then(function() { - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser2, storageManager); - }) - .then(function() { - // Move to in memory. - return userManager.setPersistence('none'); - }) - .then(function() { - // User should be switched to in-memory storage. - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'none', testUser2, storageManager); - }) - .then(function() { - // This should match. - return userManager.getCurrentUser(); - }) - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - // Internally switches to local storage. - userManager.setPersistence('local'); - // Internally switches back to session storage. - userManager.setPersistence('session'); - // This error should not affect last state change. - assertThrows(function() { - userManager.setPersistence('bla'); - }); - // Clears user (storage should be empty after). - userManager.removeCurrentUser(); - // This should be saved in sessionStorage. - userManager.setCurrentUser(testUser); - return userManager.getCurrentUser(); - }) - .then(function(user) { - // Should only be saved in session storage. - fireauth.common.testHelper.assertUserEquals(testUser, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser, storageManager); - }); -} - - -function testUserManager_existingState_setPersistence() { - // Test setPersistence behavior with some initial saved persistence state. - var storageKey = 'firebase:authUser:appId1'; - // Save initial data in local storage. - mockLocalStorage.set(storageKey, testUser2.toPlainObject()); - var storageManager = getDefaultStorageManagerInstance(); - // As no existing state, the default is local. - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - // Switch persistence to session. - userManager.setPersistence('session'); - // Should be switched to session. - return userManager.getCurrentUser() - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser2, storageManager); - }) - .then(function() { - // Simulate some state duplication due to some unexpected error. - mockLocalStorage.set(storageKey, testUser.toPlainObject()); - // Should switch state from session to none and clear everything else. - userManager.setPersistence('none'); - return userManager.getCurrentUser(); - }) - .then(function(user) { - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'none', testUser2, storageManager); - }); -} - - -function testUserManager_switchToLocalOnExternalEvents_noExistingUser() { - // Test when external storage event is detected with no existing user that - // persistence is switched to local. - var storageKey = 'firebase:authUser:appId1'; - var listener = goog.testing.recordFunction(); - // Fake storage event. - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = storageKey; - storageEvent.newValue = null; - - var storageManager = getDefaultStorageManagerInstance(); - // As no existing state, the default is local. - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - userManager.addCurrentUserChangeListener(listener); - // Should switch to session. - return userManager.setPersistence('session') - .then(function() { - // Simulate user signed in in another tab. - storageEvent.newValue = JSON.stringify(testUser2.toPlainObject()); - // This should trigger listener and switch storage from session to - // local. - mockLocalStorage.fireBrowserEvent(storageEvent); - // Listener should be called. - assertEquals(1, listener.getCallCount()); - return userManager.getCurrentUser(); - }) - .then(function(user) { - // User should be save in local storage. - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser2, storageManager); - }) - .then(function() { - userManager.removeCurrentUserChangeListener(listener); - // This should not trigger listener. - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, listener.getCallCount()); - }); -} - - -function testUserManager_switchToLocalOnExternalEvents_existingUser() { - // Test when external storage event is detected with an existing user stored - // in a non-local storage that persistence is switched to local. - var storageKey = 'firebase:authUser:appId1'; - var listener = goog.testing.recordFunction(); - // Fake storage event. - var storageEvent = - new goog.testing.events.Event(goog.events.EventType.STORAGE, window); - storageEvent.key = storageKey; - storageEvent.newValue = null; - // Existing user in session storage. - mockSessionStorage.set(storageKey, testUser.toPlainObject()); - - var storageManager = getDefaultStorageManagerInstance(); - // Due to existing state in session storage, the initial state is session. - var userManager = new fireauth.storage.UserManager('appId1', storageManager); - userManager.addCurrentUserChangeListener(listener); - // Should switch to session. - return userManager.getCurrentUser() - .then(function(user) { - fireauth.common.testHelper.assertUserEquals(testUser, user); - // Confirm user stored in session - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'session', testUser, storageManager); - }) - .then(function(user) { - // Simulate user signed in in another tab. - storageEvent.newValue = JSON.stringify(testUser2.toPlainObject()); - // This should trigger listener and switch storage to local. - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, listener.getCallCount()); - return userManager.getCurrentUser(); - }) - .then(function(user) { - // New user should be stored in local storage. - fireauth.common.testHelper.assertUserEquals(testUser2, user); - return fireauth.common.testHelper.assertUserStorage( - 'appId1', 'local', testUser2, storageManager); - }) - .then(function() { - userManager.removeCurrentUserChangeListener(listener); - // This should not trigger listener. - mockLocalStorage.fireBrowserEvent(storageEvent); - assertEquals(1, listener.getCallCount()); - }); -} diff --git a/packages/auth/test/testhelper.js b/packages/auth/test/testhelper.js deleted file mode 100644 index 89a421f6e2f..00000000000 --- a/packages/auth/test/testhelper.js +++ /dev/null @@ -1,290 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Helper functions for testing Firebase Auth common - * functionalities. - */ - -goog.provide('fireauth.common.testHelper'); - -goog.require('fireauth.storage.Factory'); -goog.require('fireauth.util'); -goog.require('goog.Promise'); -goog.require('goog.crypt.base64'); -goog.require('goog.object'); - -goog.setTestOnly('fireauth.common.testHelper'); - - -/** - * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be - * used as Internet Explorer adds the stack trace as a property of the object. - * @param {!fireauth.AuthError} expected - * @param {!fireauth.AuthError} actual - */ -fireauth.common.testHelper.assertErrorEquals = function(expected, actual) { - assertObjectEquals(expected.toPlainObject(), actual.toPlainObject()); -}; - - -/** - * Asserts that two users are equivalent. - * @param {!fireauth.AuthUser} expected The expected user. - * @param {!fireauth.AuthUser} actual The actual user. - */ -fireauth.common.testHelper.assertUserEquals = function(expected, actual) { - assertObjectEquals(expected.toPlainObject(), actual.toPlainObject()); -}; - - -/** - * Asserts that a user credential response matches the expected values. - * @param {?fireauth.AuthUser} expectedUser The user to expect. - * @param {?fireauth.AuthCredential} expectedCred The credential to expect. - * @param {?fireauth.AdditionalUserInfo} expectedAdditionalData The additional - * user info to expect. - * @param {?string|undefined} expectedOperationType The operation type to - * expect. - * @param {!fireauth.AuthEventManager.Result} response The actual response. - */ -fireauth.common.testHelper.assertUserCredentialResponse = function(expectedUser, - expectedCred, expectedAdditionalData, expectedOperationType, response) { - if (!expectedCred) { - assertTrue( - response['credential'] === null || - response['credential'] === undefined); - } else { - // Confirm property is read-only. - response['credential'] = 'should not modify property'; - assertObjectEquals(expectedCred, response['credential']); - } - if (!expectedAdditionalData) { - assertTrue( - response['additionalUserInfo'] === null || - response['additionalUserInfo'] === undefined); - } else { - // Confirm property is read-only. - response['additionalUserInfo'] = 'should not modify property'; - assertObjectEquals(expectedAdditionalData, response['additionalUserInfo']); - } - if (!expectedOperationType) { - assertTrue( - response['operationType'] === null || - response['operationType'] === undefined); - } else { - // Confirm property is read-only. - response['operationType'] = 'should not modify property'; - assertEquals(expectedOperationType, response['operationType']); - } - if (!expectedUser) { - assertNull(response['user']); - } else { - // Confirm property is read-only. - response['user'] = 'should not modify property'; - assertEquals(expectedUser, response['user']); - } -}; - - -/** - * Asserts that a popup and redirect response matches the expected values. - * @param {?fireauth.User} expectedUser The user to expect. - * @param {?fireauth.AuthCredential} expectedCred The credential to expect. - * @param {!fireauth.Auth.PopupAndRedirectResult} response The actual response. - */ -fireauth.common.testHelper.assertDeprecatedUserCredentialResponse = function( - expectedUser, expectedCred, response) { - if (!expectedCred) { - assertTrue( - response['credential'] === null || - response['credential'] === undefined); - } else { - // Confirm property is read-only. - response['credential'] = 'should not modify property'; - assertObjectEquals(expectedCred, response['credential']); - } - if (!expectedUser) { - assertNull(response['user']); - } else { - // Confirm property is read-only. - response['user'] = 'should not modify property'; - assertEquals(expectedUser, response['user']); - } - assertUndefined(response['operationType']); - assertUndefined(response['additionalUserInfo']); -}; - - -/** - * Asserts that the specified user is stored in the specified persistence and - * no where else. - * @param {string} appId The app ID used for the storage key. - * @param {?fireauth.authStorage.Persistence} persistence The persistence to - * check for existence. If null is passed, the check will ensure no user is - * saved in storage. - * @param {?fireauth.AuthUser} expectedUser The expected Auth user to test for. - * @param {?fireauth.authStorage.Manager} manager The underlying storage - * manager to use. If none is provided, the default global instance is used. - * @return {!goog.Promise} A promise that resolves when the check completes. - */ -fireauth.common.testHelper.assertUserStorage = - function(appId, persistence, expectedUser, manager) { - var promises = []; - // All supported persistence types. - var types = ['local', 'session', 'none']; - // For each persistence type. - for (var i = 0; i < types.length; i++) { - // Get the current user if stored in current persistence. - var p = manager.get({name: 'authUser', persistent: types[i]}, appId); - if (persistence === types[i]) { - // If matching specified persistence, ensure value matches the specified - // user. - promises.push(p.then(function(user) { - assertObjectEquals( - expectedUser && expectedUser.toPlainObject(), - user); - })); - } else { - // All other persistence types, should not have any value stored. - promises.push(p.then(function(user) { - assertUndefined(user); - })); - } - } - // Wait for all checks to complete before resolving. - return goog.Promise.all(promises); -}; - - -/** - * @param {!fireauth.IdTokenResult} idTokenResult The ID token result to assert. - * @param {?string} token The expected token string. - * @param {?number} expirationTime The expected expiration time in seconds. - * @param {?number} authTime The expected auth time in seconds. - * @param {?number} issuedAtTime The expected issued time in seconds. - * @param {?string} signInProvider The expected sign-in provider. - * @param {?string} signInSecondFactor The expected sign-in second factor. - * @param {!Object} claims The expected payload claims . - */ -fireauth.common.testHelper.assertIdTokenResult = function ( - idTokenResult, - token, - expirationTime, - authTime, - issuedAtTime, - signInProvider, - signInSecondFactor, - claims) { - assertEquals(token, idTokenResult['token']); - assertEquals(fireauth.util.utcTimestampToDateString(expirationTime * 1000), - idTokenResult['expirationTime']); - assertEquals(fireauth.util.utcTimestampToDateString(authTime * 1000), - idTokenResult['authTime']); - assertEquals(fireauth.util.utcTimestampToDateString(issuedAtTime * 1000), - idTokenResult['issuedAtTime']); - assertEquals(signInProvider, idTokenResult['signInProvider']); - assertEquals(signInSecondFactor, idTokenResult['signInSecondFactor']); - assertObjectEquals(claims, idTokenResult['claims']); -}; - - -/** - * Asserts that two users with differnt API keys are equivalent. Plain - * assertObjectEquals may not work as the expiration time may sometimes be off - * by a second. This takes that into account. The different App names and API - * keys will be ignored. - * @param {!fireauth.AuthUser} expected - * @param {!fireauth.AuthUser} actual - * @param {string=} opt_expectedApikey - * @param {string=} opt_actualApikey - */ -fireauth.common.testHelper.assertUserEqualsInWithDiffApikey = function( - expected, actual, opt_expectedApikey, opt_actualApikey) { - var expectedObj = expected.toPlainObject(); - var actualObj = actual.toPlainObject(); - if (opt_expectedApikey && opt_actualApikey) { - assertEquals(opt_expectedApikey, expectedObj['apiKey']); - assertEquals(opt_actualApikey, actualObj['apiKey']); - // Overwrite ApiKeys. - expectedObj['apiKey'] = ''; - actualObj['apiKey'] = ''; - expectedObj['stsTokenManager']['apiKey'] = ''; - actualObj['stsTokenManager']['apiKey'] = ''; - } - expectedObj['appName'] = ''; - actualObj['appName'] = ''; - assertObjectEquals(expectedObj, actualObj); -}; - - -/** - * Installs different persistent/temporary storage using the provided mocks. - * @param {!goog.testing.PropertyReplacer} stub The property replacer. - * @param {!fireauth.storage.Storage} mockLocalStorage The mock storage - * instance for persistent storage. - * @param {!fireauth.storage.Storage} mockSessionStorage The mock storage - * instance for temporary storage. - */ -fireauth.common.testHelper.installMockStorages = - function(stub, mockLocalStorage, mockSessionStorage) { - stub.replace( - fireauth.storage.Factory.prototype, - 'makePersistentStorage', - function() { - return mockLocalStorage; - }); - stub.replace( - fireauth.storage.Factory.prototype, - 'makeTemporaryStorage', - function() { - return mockSessionStorage; - }); -}; - - -/** - * Creates a mock Firebase ID token JWT with the provided optional payload and - * optional expiration time. - * @param {?Object=} opt_payload The optional payload used to override default - * hardcoded values. - * @param {number=} opt_expirationTime The optional expiration time in - * milliseconds. - * @return {string} The mock JWT. - */ -fireauth.common.testHelper.createMockJwt = - function(payload, expirationTime) { - // JWT time units should not have decimals but to make testing easier, - // we will allow it. - const now = Date.now() / 1000; - const basePayload = { - 'iss': 'https://securetoken.google.com/projectId', - 'aud': 'projectId', - 'sub': '12345678', - 'auth_time': now, - 'iat': now, - 'exp': typeof expirationTime === 'undefined' ? - now + 3600 : expirationTime / 1000 - }; - // Extend base payload. - Object.assign(basePayload, payload || {}); - const encodedPayload = - goog.crypt.base64.encodeString(JSON.stringify(basePayload), - goog.crypt.base64.Alphabet.WEBSAFE); - // Remove any trailing or leading dots from the payload component. - return 'HEAD.' + encodedPayload.replace(/^\.+|\.+$/g, '') + '.SIGNATURE'; -}; diff --git a/packages/auth/test/token_test.js b/packages/auth/test/token_test.js deleted file mode 100644 index e9ff013460b..00000000000 --- a/packages/auth/test/token_test.js +++ /dev/null @@ -1,660 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for token.js - */ - -goog.provide('fireauth.StsTokenManagerTest'); - -goog.require('fireauth.AuthError'); -goog.require('fireauth.RpcHandler'); -goog.require('fireauth.StsTokenManager'); -goog.require('fireauth.authenum.Error'); -goog.require('fireauth.common.testHelper'); -goog.require('goog.Promise'); -goog.require('goog.testing.AsyncTestCase'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.StsTokenManagerTest'); - - -let token = null; -let rpcHandler = null; -const stubs = new goog.testing.PropertyReplacer(); -const asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); -const now = Date.now(); - - -function setUp() { - // Override now. - stubs.replace(Date, 'now', () => { - return now; - }); - // Initialize RPC handler and token. - rpcHandler = new fireauth.RpcHandler( - 'apiKey', - { - 'tokenEndpoint': 'https://securetoken.googleapis.com/v1/token', - 'tokenTimeout': 10000, - 'tokenHeaders': { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }); - token = new fireauth.StsTokenManager(rpcHandler); -} - - -function tearDown() { - // Reset property replacer, token and RPC handler. - token = null; - rpcHandler = null; - stubs.reset(); -} - - -/** - * Helper function to check RPC requestStsToken parameters and simulate a - * returned response. - * @param {?Object|string} expectedData The expected body data. - * @param {?Object} xhrResponse The returned response when no error is returned. - * @param {?fireauth.AuthError=} error The specific error returned. - */ -function assertRpcHandler( - expectedData, - xhrResponse, - error) { - stubs.replace( - fireauth.RpcHandler.prototype, - 'requestStsToken', - function(data) { - return new goog.Promise(function(resolve, reject) { - // Confirm expected data sent. - assertObjectEquals(expectedData, data); - if (xhrResponse) { - // Return expected response. - resolve(xhrResponse); - } else if (error) { - reject(error); - } else { - reject( - new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR)); - } - }); - }); -} - - -/** - * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be - * used as Internet Explorer adds the stack trace as a property of the object. - * @param {!Error} expected - * @param {!Error} actual - */ -function assertErrorEquals(expected, actual) { - assertEquals(expected.code, actual.code); - assertEquals(expected.message, actual.message); -} - - -function testStsTokenManager_gettersSetters() { - const expirationTime = now + 3600 * 1000; - const jwt = fireauth.common.testHelper.createMockJwt(null, expirationTime); - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresAt(expirationTime); - assertEquals('refreshToken', token.getRefreshToken()); - assertEquals(token.getExpirationTime(), expirationTime); -} - - -function testStsTokenManager_parseServerResponse() { - // We should prefer the local clock + expiresIn to the expiration time in the - // JWT. - const expirationTimeServer = now + 4800 * 1000; - const expirationTimeClient = now + 3600 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTimeServer); - const serverResponse = { - 'idToken': jwt, - 'refreshToken': 'myStsRefreshToken', - 'expiresIn': '3600' - }; - const accessToken = token.parseServerResponse(serverResponse); - assertEquals(jwt, accessToken); - assertEquals('myStsRefreshToken', token.getRefreshToken()); - assertEquals(token.getExpirationTime(), expirationTimeClient); -} - - -function testStsTokenManager_parseServerResponse_noExpiresIn() { - // If response does not contain expiresIn, we should fallback to using the - // delta between the exp and iat in the JWT. - const expirationTimeServer = now + 3600 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTimeServer); - const serverResponse = {'idToken': jwt, 'refreshToken': 'myStsRefreshToken'}; - const accessToken = token.parseServerResponse(serverResponse); - assertEquals(jwt, accessToken); - assertEquals('myStsRefreshToken', token.getRefreshToken()); - assertEquals(expirationTimeServer, token.getExpirationTime()); -} - - -function testStsTokenManager_toServerResponse() { - const expirationTime = now + 3600 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTime); - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresAt(expirationTime); - assertObjectEquals( - {'refreshToken': 'refreshToken', 'idToken': jwt}, - token.toServerResponse()); -} - - -function testStsTokenManager_copy() { - const expirationTime = now + 3600 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTime); - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresAt(expirationTime); - // Injects a new RPC handler with different API key. - const rpcHandlerWithDiffApiKey = new fireauth.RpcHandler('apiKey2', { - 'tokenEndpoint': 'https://securetoken.googleapis.com/v1/token', - 'tokenTimeout': 10000, - 'tokenHeaders': {'Content-Type': 'application/x-www-form-urlencoded'} - }); - const tokenToCopy = new fireauth.StsTokenManager(rpcHandlerWithDiffApiKey); - const newExpirationTime = now + 4800 * 1000; - const newJwt = - fireauth.common.testHelper.createMockJwt(null, newExpirationTime); - const serverResponse = { - 'idToken': newJwt, - 'refreshToken': 'newRefreshToken', - 'expiresIn': '4800' - }; - - tokenToCopy.parseServerResponse(serverResponse); - token.copy(tokenToCopy); - assertObjectEquals( - { - // ApiKey should remain the same. - 'apiKey': 'apiKey', - 'refreshToken': 'newRefreshToken', - 'accessToken': newJwt, - 'expirationTime': token.getExpirationTime() - }, - token.toPlainObject()); - assertEquals(token.getExpirationTime(), newExpirationTime); -} - - -function testStsTokenManager_copy_withClockSkew() { - const expirationTime = now + 3600 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTime); - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresAt(expirationTime); - // Injects a new RPC handler with different API key. - const rpcHandlerWithDiffApiKey = new fireauth.RpcHandler('apiKey2', { - 'tokenEndpoint': 'https://securetoken.googleapis.com/v1/token', - 'tokenTimeout': 10000, - 'tokenHeaders': {'Content-Type': 'application/x-www-form-urlencoded'} - }); - const tokenToCopy = new fireauth.StsTokenManager(rpcHandlerWithDiffApiKey); - const newExpirationTimeServer = now + 1200 * 1000; - const newExpirationTimeClient = now + 4800 * 1000; - const newJwt = fireauth.common.testHelper.createMockJwt( - null, newExpirationTimeServer); - const serverResponse = { - 'idToken': newJwt, - 'refreshToken': 'newRefreshToken', - 'expiresIn': '4800' - }; - - tokenToCopy.parseServerResponse(serverResponse); - token.copy(tokenToCopy); - assertObjectEquals( - { - // ApiKey should remain the same. - 'apiKey': 'apiKey', - 'refreshToken': 'newRefreshToken', - 'accessToken': newJwt, - 'expirationTime': token.getExpirationTime() - }, - token.toPlainObject()); - assertEquals(token.getExpirationTime(), newExpirationTimeClient); -} - - -function testStsTokenManager_getToken_noToken() { - // No token. - asyncTestCase.waitForSignals(1); - token.getToken().then((token) => { - assertNull(token); - asyncTestCase.signal(); - }); -} - - -function testStsTokenManager_getToken_invalidResponse() { - // Test when an network error is returned and then called again successfully - // that the network error is not cached. - const expectedError = - new fireauth.AuthError(fireauth.authenum.Error.NETWORK_REQUEST_FAILED); - const jwt = fireauth.common.testHelper.createMockJwt(); - token.setRefreshToken('myRefreshToken'); - // Simulate invalid response from server. - assertRpcHandler( - { - 'grant_type': 'refresh_token', - 'refresh_token': 'myRefreshToken' - }, - null, - expectedError); - asyncTestCase.waitForSignals(1); - token.getToken().then(fail, (error) => { - // Invalid response error should be triggered. - assertErrorEquals(expectedError, error); - // Since this is not an expired token error, another call should still - // ping the backend. - assertRpcHandler( - { - 'grant_type': 'refresh_token', - 'refresh_token': 'myRefreshToken' - }, - { - 'access_token': jwt, - 'refresh_token': 'refreshToken2', - 'expires_in': '3600' - }); - token.getToken().then(function(response) { - // Confirm all properties updated. - assertEquals(jwt, response['accessToken']); - assertEquals('refreshToken2', response['refreshToken']); - asyncTestCase.signal(); - }); - }); -} - - -function testStsTokenManager_getToken_tokenExpiredError() { - // Test when expired refresh token error is returned. - // Simulate Id token is expired to force refresh. - const expirationTime = now - 1; - // Expected token expired error. - const expectedError = - new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED); - const expiredJwt = fireauth.common.testHelper.createMockJwt( - null, expirationTime); - const newJwt = fireauth.common.testHelper.createMockJwt(); - token.setAccessToken(expiredJwt); - token.setRefreshToken('myRefreshToken'); - token.setExpiresAt(expirationTime); - assertFalse(token.isRefreshTokenExpired()); - // Simulate token expired error from server. - assertRpcHandler( - { - 'grant_type': 'refresh_token', - 'refresh_token': 'myRefreshToken' - }, - null, - expectedError); - asyncTestCase.waitForSignals(4); - // This call will return token expired error. - token.getToken().then(fail, (error) => { - assertTrue(token.isRefreshTokenExpired()); - // If another RPC is sent, it will resolve with valid STS token. - // This should not happen since the token expired error is cached. - assertRpcHandler( - { - 'grant_type': 'refresh_token', - 'refresh_token': 'refreshToken2' - }, - { - 'access_token': newJwt, - 'refresh_token': 'refreshToken2', - 'expires_in': '3600' - }); - // Token expired error should be thrown. - assertErrorEquals(expectedError, error); - // Try again, cached expired token error should be triggered. - token.getToken(false).thenCatch((error) => { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // Try again with forced refresh, cached expired token error should be - // triggered. - token.getToken(true).thenCatch((error) => { - assertErrorEquals(expectedError, error); - asyncTestCase.signal(); - }); - // Plain object should have refresh token reset. - assertObjectEquals( - { - 'apiKey': 'apiKey', - 'refreshToken': null, - 'accessToken': expiredJwt, - 'expirationTime': expirationTime - }, - token.toPlainObject()); - // If the refresh token is manually updated, the cached error should be - // cleared. - token.setRefreshToken('refreshToken2'); - // This should now resolve. - token.getToken().then((response) => { - assertFalse(token.isRefreshTokenExpired()); - // Confirm all properties updated. - assertEquals(newJwt, response['accessToken']); - assertEquals('refreshToken2', response['refreshToken']); - // Plain object should have the new refresh token set. - assertObjectEquals( - { - 'apiKey': 'apiKey', - 'refreshToken': 'refreshToken2', - 'accessToken': newJwt, - 'expirationTime': token.getExpirationTime() - }, - token.toPlainObject()); - assertEquals(token.getExpirationTime(), now + 3600 * 1000); - asyncTestCase.signal(); - }); - asyncTestCase.signal(); - }); -} - - -function testStsTokenManager_getToken_withClockSkew_clientBehind() { - // Server clock is ahead, so it looks like the token is not yet expired. - const expirationTimeServer = now + 900 * 1000; - // Client should realize that the token is actually expired. - const expirationTimeClient = now - 300 * 1000; - const expiredJwt = - fireauth.common.testHelper.createMockJwt(null, expirationTimeServer); - const newJwtExpirationServer = now + 4800 * 1000; - const newJwtExpirationClient = now + 3600 * 1000; - const newJwt = fireauth.common.testHelper.createMockJwt( - null, newJwtExpirationServer); - token.setRefreshToken('refreshToken'); - // Expired access token. - token.setAccessToken(expiredJwt); - token.setExpiresAt(expirationTimeClient); - // It will attempt to exchange refresh token for STS token. - assertRpcHandler( - {'grant_type': 'refresh_token', 'refresh_token': 'refreshToken'}, { - 'access_token': newJwt, - 'refresh_token': 'refreshToken2', - 'expires_in': '3600' - }); - asyncTestCase.waitForSignals(1); - token.getToken().then((response) => { - // Confirm all properties updated. - assertEquals('refreshToken2', token.getRefreshToken()); - assertEquals(token.getExpirationTime(), newJwtExpirationClient); - // Confirm correct STS response. - assertObjectEquals( - {'accessToken': newJwt, 'refreshToken': 'refreshToken2'}, response); - asyncTestCase.signal(); - }); -} - - -function testStsTokenManager_getToken_withClockSkew_clientAhead() { - // Server clock is behind, so token looks like it's expired. - const expirationTimeServer = now - 1 * 1000; - // Client should realize that the token is not actually expired. - const expirationTimeClient = now + 1200 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTimeServer); - token.setRefreshToken('refreshToken'); - // Not yet expired access token. - token.setAccessToken(jwt); - token.setExpiresAt(expirationTimeClient); - asyncTestCase.waitForSignals(1); - token.getToken().then((response) => { - assertEquals('refreshToken', token.getRefreshToken()); - assertEquals(token.getExpirationTime(), expirationTimeClient); - // Confirm correct STS response. - assertObjectEquals( - {'accessToken': jwt, 'refreshToken': 'refreshToken'}, response); - asyncTestCase.signal(); - }); -} - - -function testStsTokenManager_getToken_tokenAlmostExpired() { - // Test when cached token is within range of being almost expired. - const expirationTime = - now + fireauth.StsTokenManager.TOKEN_REFRESH_BUFFER - 1; - const almostExpiredJwt = - fireauth.common.testHelper.createMockJwt(null, expirationTime); - const newJwt = fireauth.common.testHelper.createMockJwt(); - token.setAccessToken(almostExpiredJwt); - token.setRefreshToken('myRefreshToken'); - token.setExpiresAt(expirationTime); - assertFalse(token.isRefreshTokenExpired()); - // Token will be refreshed since cached token is almost expired. - assertRpcHandler( - {'grant_type': 'refresh_token', 'refresh_token': 'myRefreshToken'}, { - 'access_token': newJwt, - 'refresh_token': 'refreshToken2', - 'expires_in': '3600' - }); - asyncTestCase.waitForSignals(1); - token.getToken().then((response) => { - assertFalse(token.isRefreshTokenExpired()); - // Confirm all properties updated. - assertEquals(newJwt, response['accessToken']); - assertEquals('refreshToken2', response['refreshToken']); - assertObjectEquals( - { - 'apiKey': 'apiKey', - 'refreshToken': 'refreshToken2', - 'accessToken': newJwt, - 'expirationTime': token.getExpirationTime() - }, - token.toPlainObject()); - asyncTestCase.signal(); - }); -} - - -function testStsTokenManager_getToken_exchangeRefreshToken() { - // Set a previously cached access token that is expired. - const expirationTime = now - 1000; - const unexpiredTime = now + 3600 * 1000; - const expiredJwt = - fireauth.common.testHelper.createMockJwt(null, expirationTime); - const newJwt = - fireauth.common.testHelper.createMockJwt(null, unexpiredTime); - token.setRefreshToken('refreshToken'); - // Expired access token. - token.setAccessToken(expiredJwt); - token.setExpiresAt(expirationTime); - // It will attempt to exchange refresh token for STS token. - assertRpcHandler( - { - 'grant_type': 'refresh_token', - 'refresh_token': 'refreshToken' - }, - { - 'access_token': newJwt, - 'refresh_token': 'refreshToken2', - 'expires_in': '3600' - }); - asyncTestCase.waitForSignals(1); - token.getToken().then((response) => { - // Confirm all properties updated. - assertEquals('refreshToken2', token.getRefreshToken()); - assertEquals(token.getExpirationTime(), unexpiredTime); - // Confirm correct STS response. - assertObjectEquals( - { - 'accessToken': newJwt, - 'refreshToken': 'refreshToken2' - }, - response); - asyncTestCase.signal(); - }); -} - - -function testStsTokenManager_getToken_cached() { - // Set a previously cached access token that hasn't expired yet. - const expirationTime = now + 60 * 1000; - const jwt = fireauth.common.testHelper.createMockJwt(null, expirationTime); - // Set refresh token and unexpired access token. - // No XHR request needed. - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresAt(expirationTime); - asyncTestCase.waitForSignals(1); - token.getToken().then((response) => { - assertEquals('refreshToken', token.getRefreshToken()); - assertEquals(token.getExpirationTime(), expirationTime); - // Confirm correct STS response. - assertObjectEquals( - { - 'accessToken': jwt, - 'refreshToken': 'refreshToken' - }, - response); - asyncTestCase.signal(); - }); -} - - -function testStsTokenManager_getToken_forceRefresh() { - // Set a previously cached access token that hasn't expired yet. - const expirationTime = now + 3000 * 1000; - const expirationTime2 = now + 3600 * 1000; - const jwt = fireauth.common.testHelper.createMockJwt(null, expirationTime); - const newJwt = fireauth.common.testHelper.createMockJwt(null, expirationTime2); - // Set ID token, refresh token and unexpired access token. - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresAt(expirationTime); - // Even though unexpired access token, it will attempt to exchange for refresh - // token since force refresh is set to true. - assertRpcHandler( - { - 'grant_type': 'refresh_token', - 'refresh_token': 'refreshToken' - }, - { - 'access_token': newJwt, - 'refresh_token': 'refreshToken2', - 'expires_in': '3600' - }); - asyncTestCase.waitForSignals(1); - token.getToken(true).then((response) => { - // Confirm all properties updated. - assertEquals('refreshToken2', token.getRefreshToken()); - assertEquals(token.getExpirationTime(), expirationTime2); - // Confirm correct STS response. - assertObjectEquals( - { - 'accessToken': newJwt, - 'refreshToken': 'refreshToken2' - }, - response); - asyncTestCase.signal(); - }); -} - - -function testToPlainObject() { - const expirationTime = now + 3600 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTime); - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresAt(expirationTime); - assertObjectEquals( - { - 'apiKey': 'apiKey', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': expirationTime - }, - token.toPlainObject()); -} - - -function testToPlainObject_withClockSkew() { - const expirationTimeServer = now + 1200 * 1000; - const expirationTimeClient = now + 3600 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTimeServer); - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresIn(3600); - assertObjectEquals( - { - 'apiKey': 'apiKey', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': token.getExpirationTime() - }, - token.toPlainObject()); - assertEquals(expirationTimeClient, token.getExpirationTime()); -} - - -function testFromPlainObject() { - const expirationTime = now + 3600 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTime); - assertNull(fireauth.StsTokenManager.fromPlainObject( - new fireauth.RpcHandler('apiKey'), {})); - - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresAt(expirationTime); - assertObjectEquals( - token, fireauth.StsTokenManager.fromPlainObject(rpcHandler, { - 'apiKey': 'apiKey', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': expirationTime - })); -} - - -function testFromPlainObject_withClockSkew() { - const expirationTimeServer = now + 1200 * 1000; - const jwt = - fireauth.common.testHelper.createMockJwt(null, expirationTimeServer); - assertNull(fireauth.StsTokenManager.fromPlainObject( - new fireauth.RpcHandler('apiKey'), {})); - - token.setRefreshToken('refreshToken'); - token.setAccessToken(jwt); - token.setExpiresIn(3600); - assertObjectEquals( - token, fireauth.StsTokenManager.fromPlainObject(rpcHandler, { - 'apiKey': 'apiKey', - 'refreshToken': 'refreshToken', - 'accessToken': jwt, - 'expirationTime': token.getExpirationTime() - })); -} \ No newline at end of file diff --git a/packages/auth/test/universallinksubscriber_test.js b/packages/auth/test/universallinksubscriber_test.js deleted file mode 100644 index b5a74510dc6..00000000000 --- a/packages/auth/test/universallinksubscriber_test.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for universallinksubscriber.js - */ - -goog.provide('fireauth.UniversalLinkSubscriberTest'); - -goog.require('fireauth.UniversalLinkSubscriber'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('fireauth.UniversalLinkSubscriberTest'); - - -var universalLinks = null; -var eventData1 = {url: 'URL1'}; -var eventData2 = {url: 'URLK2'}; -var eventData3 = {url: 'URLK2'}; - - -function setUp() { - // Initialize mock universalLinks plugin. - universalLinks = {}; - universalLinks.subscribe = function(name, cb) { - // Only one subscriber allowed. - assertUndefined(universalLinks.cb); - // No name should be supplied. - assertNull(name); - // Save callback. - universalLinks.cb = cb; - }; - -} - - -function testUniversalLinkSubscriber() { - var listener1 = goog.testing.recordFunction(); - var listener2 = goog.testing.recordFunction(); - var universalLinkSubscriber = new fireauth.UniversalLinkSubscriber(); - universalLinkSubscriber.subscribe(listener1); - universalLinkSubscriber.subscribe(listener2); - // Trigger first event. - universalLinks.cb(eventData1); - // Both listeners triggered. - assertEquals(1, listener1.getCallCount()); - assertEquals(eventData1, listener1.getLastCall().getArgument(0)); - assertEquals(1, listener2.getCallCount()); - assertEquals(eventData1, listener2.getLastCall().getArgument(0)); - - // Remove listener 2. - universalLinkSubscriber.unsubscribe(listener2); - // Trigger second event. - universalLinks.cb(eventData2); - // Only first listener triggered. - assertEquals(2, listener1.getCallCount()); - assertEquals(eventData2, listener1.getLastCall().getArgument(0)); - assertEquals(1, listener2.getCallCount()); - - // Remove remaining listener. - universalLinkSubscriber.unsubscribe(listener1); - // Trigger third event. - universalLinks.cb(eventData3); - // No listener triggered - assertEquals(2, listener1.getCallCount()); - assertEquals(1, listener2.getCallCount()); - - // Re-subscribe first listener. - universalLinkSubscriber.subscribe(listener1); - // Trigger first event again. - universalLinks.cb(eventData1); - // Only first listener triggered. - assertEquals(3, listener1.getCallCount()); - assertEquals(eventData1, listener1.getLastCall().getArgument(0)); - assertEquals(1, listener2.getCallCount()); -} - - -function testUniversalLinkSubscriber_unavailable() { - // Test when universal link plugin not available, no error is thrown. - universalLinks = null; - var listener1 = goog.testing.recordFunction(); - var universalLinkSubscriber = new fireauth.UniversalLinkSubscriber(); - assertNotThrows(function() { - universalLinkSubscriber.subscribe(listener1); - universalLinkSubscriber.unsubscribe(listener1); - }); -} - - -function testUniversalLinkSubscriber_clear() { - var instance = fireauth.UniversalLinkSubscriber.getInstance(); - // Same instance should be returned. - assertEquals(instance, fireauth.UniversalLinkSubscriber.getInstance()); - fireauth.UniversalLinkSubscriber.clear(); - // After clearing, new instance should be returned. - assertNotEquals(instance, fireauth.UniversalLinkSubscriber.getInstance()); -} - diff --git a/packages/auth/test/userevent_test.js b/packages/auth/test/userevent_test.js deleted file mode 100644 index 5aab088a6f1..00000000000 --- a/packages/auth/test/userevent_test.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for userevent.js - */ - -goog.provide('fireauth.UserEventTest'); - -goog.require('fireauth.UserEvent'); -goog.require('fireauth.UserEventType'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('fireauth.UserEventTest'); - - -function testUserEvent() { - var props = { - a: 'foo', - b: true, - c: -1.5, - d: { - e: 'bar' - } - }; - var event = new fireauth.UserEvent( - fireauth.UserEventType.TOKEN_CHANGED, - props); - assertEquals(fireauth.UserEventType.TOKEN_CHANGED, event.type); - for (var key in props) { - assertEquals(props[key], event[key]); - } -} diff --git a/packages/auth/test/utils_test.js b/packages/auth/test/utils_test.js deleted file mode 100644 index b474ac5a936..00000000000 --- a/packages/auth/test/utils_test.js +++ /dev/null @@ -1,1956 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Tests for utils.js - */ - -goog.provide('fireauth.utilTest'); - -goog.require('fireauth.util'); -goog.require('goog.Timer'); -goog.require('goog.dom'); -goog.require('goog.testing.MockControl'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.TestCase'); -goog.require('goog.testing.jsunit'); -goog.require('goog.userAgent'); - -goog.setTestOnly('fireauth.utilTest'); - - -var mockControl; -var stubs = new goog.testing.PropertyReplacer(); - -var operaUA = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHT' + - 'ML, like Gecko) Chrome/49.0.2623.110 Safari/537.36 OPR/36.0.2130.74'; -var ieUA = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;' + - ' SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; ' + - 'Media Center PC 6.0; .NET4.0C)'; -var edgeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' + - '(KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240'; -var firefoxUA = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/201' + - '00101 Firefox/46.0'; -var silkUA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, li' + - 'ke Gecko) Silk/44.1.54 like Chrome/44.0.2403.63 Safari/537.36'; -var safariUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11-4) AppleWebKit' + - '/601.5.17 (KHTML, like Gecko) Version/9.1 Safari/601.5.17'; -var chromeUA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, ' + - 'like Gecko) Chrome/50.0.2661.94 Safari/537.36'; -var iOS8iPhoneUA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) A' + - 'ppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safar' + - 'i/600.1.4'; -var iOS7iPodUA = 'Mozilla/5.0 (iPod touch; CPU iPhone OS 7_0_3 like Mac OS ' + - 'X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B511 ' + - 'Safari/9537.53'; -var iOS7iPadUA = 'Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/' + - '537.51.1 (KHTML, like Gecko) CriOS/30.0.1599.12 Mobile/11A465 Safari/8' + - '536.25 (3B92C18B-D9DE-4CB7-A02A-22FD2AF17C8F)'; -var iOS7iPhoneUA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0_4 like Mac OS X)' + - 'AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Sa' + - 'fari/9537.53'; -var androidUA = 'Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Buil' + - 'd/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Sa' + - 'fari/534.30'; -var blackberryUA = 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleW' + - 'ebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.' + - '11+'; -var webOSUA = 'Mozilla/5.0 (webOS/1.3; U; en-US) AppleWebKit/525.27.1 (KHTM' + - 'L, like Gecko) Version/1.0 Safari/525.27.1 Desktop/1.0'; -var windowsPhoneUA = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0' + - ';Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)'; -var chriosUA = 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; ' + - 'en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile' + - '/9B206 Safari/7534.48.3'; -var iOS9iPhoneUA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) A' + - 'ppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13C75 Safar' + - 'i/601.1'; -// This user agent is manually constructed and not copied from a production -// user agent. -var chrome55iOS10UA = 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 10_2_0 like Ma' + - 'c OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/55.0.2883.7' + - '9 Mobile/9B206 Safari/7534.48.3'; - - -var jsonString = '{"a":2,"b":["Hello","World"],"c":{"someKeyName":true,' + - '"someOtherKeyName":false}}'; -var parsedJSON = { - 'a': 2, - 'b': ['Hello', 'World'], - 'c': { - 'someKeyName': true, - 'someOtherKeyName': false - } -}; -var lastMetaTag; - - -function setUp() { - mockControl = new goog.testing.MockControl(); -} - - -function tearDown() { - mockControl.$tearDown(); - angular = undefined; - stubs.reset(); - if (lastMetaTag) { - goog.dom.removeNode(lastMetaTag); - lastMetaTag = null; - } -} - - -if (goog.global['window'] && - typeof goog.global['window'].CustomEvent !== 'function') { - var doc = goog.global.document; - /** - * CustomEvent polyfill for IE 9, 10 and 11. - * https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent - * @param {string} event The event type. - * @param {Object=} opt_params The optional event parameters. - * @return {!Event} The generated custom event. - */ - var CustomEvent = function(event, opt_params) { - var params = opt_params || { - bubbles: false, cancelable: false, detail: undefined - }; - var evt = doc.createEvent('CustomEvent'); - evt.initCustomEvent( - event, params.bubbles, params.cancelable, params.detail); - return evt; - }; - CustomEvent.prototype = goog.global['window'].Event.prototype; - goog.global['window'].CustomEvent = CustomEvent; -} - - -/** - * Install the test to run and runs it. - * @param {string} id The test identifier. - * @param {function():!goog.Promise} func The test function to run. - * @return {!goog.Promise} The result of the test. - */ -function installAndRunTest(id, func) { - var testCase = new goog.testing.TestCase(); - testCase.addNewTest(id, func); - return testCase.runTestsReturningPromise().then(function(result) { - assertTrue(result.complete); - // Display error detected. - if (result.errors.length) { - fail(result.errors.join('\n')); - } - assertEquals(1, result.totalCount); - assertEquals(1, result.runCount); - assertEquals(1, result.successCount); - assertEquals(0, result.errors.length); - }); -} - - -function testIsIe11() { - if (goog.userAgent.IE && - !!goog.userAgent.DOCUMENT_MODE && - goog.userAgent.DOCUMENT_MODE == 11) { - assertTrue(fireauth.util.isIe11()); - } else { - assertFalse(fireauth.util.isIe11()); - } -} - - -function testIsIe10() { - if (goog.userAgent.IE && - !!goog.userAgent.DOCUMENT_MODE && - goog.userAgent.DOCUMENT_MODE == 10) { - assertTrue(fireauth.util.isIe10()); - } else { - assertFalse(fireauth.util.isIe10()); - } -} - - -function testIsEdge() { - var EDGE_UA = - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' + - '(KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240'; - var CHROME_DESKTOP_UA = - 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' + - '(KHTML, like Gecko) Chrome/27.0.1453.15 Safari/537.36'; - assertTrue(fireauth.util.isEdge(EDGE_UA)); - assertFalse(fireauth.util.isEdge(CHROME_DESKTOP_UA)); -} - - -function testGetCurrentUrl() { - assertEquals(window.location.href, fireauth.util.getCurrentUrl()); -} - - -function testSanitizeRequestUri() { - // Simulate AngularJS defined. - angular = {}; - assertEquals( - 'http://localhost/path/#abc', - fireauth.util.sanitizeRequestUri( - 'http://localhost/path/#abc')); - assertEquals( - 'http://localhost/path/?query1=value1&query2=value2#abc', - fireauth.util.sanitizeRequestUri( - 'http://localhost/path/?query1=value1&query2=value2#abc')); - // Modified url with #/, should be replace with #. - assertEquals( - 'http://localhost/path#abc', - fireauth.util.sanitizeRequestUri( - 'http://localhost/path#/abc')); - // Modified url with #!/, should be replace with #. - assertEquals( - 'http://localhost/path#abc', - fireauth.util.sanitizeRequestUri( - 'http://localhost/path#!/abc')); -} - - -function testGoTo() { - var fakeWindow = { - location: { - href: '' - } - }; - fireauth.util.goTo('http://www.google.com', fakeWindow); - assertEquals('http://www.google.com', fakeWindow.location.href); - fireauth.util.goTo('http://www.google.com/some?complicated=path', fakeWindow); - assertEquals('http://www.google.com/some?complicated=path', - fakeWindow.location.href); -} - - -function testGetKeyDiff() { - var a = {'key1': 'a', 'key2': 'b'}; - var b = {'key2': 'b', 'key3': 'c'}; - assertArrayEquals( - ['key1', 'key3'], - fireauth.util.getKeyDiff(a, b)); - var c = {'key1': {'c': 3, 'd': 4}, 'key2': [5, 6], 'key3': {'a': 1, 'b': 2}}; - var d = {'key1': {'c': 3, 'd': 4}, 'key2': [5, 6], 'key3': {'a': 1, 'b': 3}}; - assertArrayEquals( - ['key3'], - fireauth.util.getKeyDiff(c, d)); - var e = {'key1': {'c': 3, 'd': 4}, 'key2': [5, 6], 'key3': {'a': 1, 'b': 2}}; - var f = {'key1': {'c': 3, 'd': 4}, 'key2': [5, 7], 'key3': {'a': 1, 'b': 3}}; - assertArrayEquals( - ['key2', 'key3'], - fireauth.util.getKeyDiff(e, f)); - var g = {'key1': null, 'key2': null}; - var h = {'key1': null, 'key2': null}; - assertArrayEquals([], fireauth.util.getKeyDiff(g, h)); - var i = {'key1': null, 'key2': null, 'key3': {}}; - var j = {'key1': null, 'key2': null, 'key3': null}; - assertArrayEquals(['key3'], fireauth.util.getKeyDiff(i, j)); - // Verifies that if value is array, elements of the array should be - // compared by value rather than by reference. - var k = {'key1': {'c': 3, 'd': 4}, 'key2': [{'a': 1, 'b': 2}]}; - var l = {'key1': {'c': 3, 'd': 4}, 'key2': [{'a': 1, 'b': 2}]}; - assertArrayEquals( - [], - fireauth.util.getKeyDiff(k, l)); - var m = {'key1': [{'a': [{'c': 1}], 'b': 2}]}; - var n = {'key1': [{'a': [{'c': 1}], 'b': 2}]}; - assertArrayEquals( - [], - fireauth.util.getKeyDiff(m, n)); - var o = {'key1': [{'a': 1, 'b': 2}, {'c': 3, 'd': 4}]}; - var p = {'key1': [{'c': 3, 'd': 4}, {'a': 1, 'b': 2}]}; - assertArrayEquals( - ['key1'], - fireauth.util.getKeyDiff(o, p)); -} - - -function testOnPopupClose() { - return installAndRunTest('onPopupClose', function() { - var win = {}; - - // Simulate close after 50ms. - goog.Timer.promise(10).then(function() { - assertFalse(!!win.closed); - win.closed = true; - }); - assertFalse(!!win.closed); - // Check every 10ms that popup is closed. - return fireauth.util.onPopupClose(win, 2).then(function() { - assertTrue(win.closed); - }); - }); -} - - -function testIsAuthorizedDomain() { - assertFalse( - fireauth.util.isAuthorizedDomain( - [], - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456/popup.html')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['chrome-extension://abcdefghijklmnopqrstuvwxyz123456'], - 'http://aihpiglmnhnhijdnjghpfnlledckkhja/abc?a=1#b=2')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['chrome-extension://abcdefghijklmnopqrstuvwxyz123456'], - 'file://aihpiglmnhnhijdnjghpfnlledckkhja/abc?a=1#b=2')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['chrome-extenion://abcdefghijklmnopqrstuvwxyz123456', - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456_suffix', - 'chrome-extension://prefix_abcdefghijklmnopqrstuvwxyz123456', - 'chrome-extension://prefix_abcdefghijklmnopqrstuvwxyz123456_suffix', - 'abcdefghijklmnopqrstuvwxyz123456'], - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456/popup.html')); - assertTrue( - fireauth.util.isAuthorizedDomain( - ['chrome-extension://abcdefghijklmnopqrstuvwxyz123456'], - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456/popup.html')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['abcdefghijklmnopqrstuvwxyz123456'], - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456/popup.html')); - assertFalse(fireauth.util.isAuthorizedDomain([], 'http://www.domain.com')); - assertTrue( - fireauth.util.isAuthorizedDomain( - ['other.com', 'domain.com'], 'http://www.domain.com/abc?a=1#b=2')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['other.com', 'example.com'], 'http://www.domain.com/abc?a=1#b=2')); - assertTrue( - fireauth.util.isAuthorizedDomain( - ['domain.com', 'domain.com.lb'], - 'http://www.domain.com.lb:8080/abc?a=1#b=2')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['domain.com', 'domain.com.mx'], - 'http://www.domain.com.lb/abc?a=1#b=2')); - // Check for suffix matching. - assertFalse( - fireauth.util.isAuthorizedDomain( - ['site.example.com'], - 'http://prefix-site.example.com')); - assertTrue( - fireauth.util.isAuthorizedDomain( - ['site.example.com'], 'https://www.site.example.com')); - // Check for IP addresses. - assertTrue( - fireauth.util.isAuthorizedDomain( - ['127.0.0.1'], 'http://127.0.0.1:8080/?redirect=132.0.0.1')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['132.0.0.1'], 'http://127.0.0.1:8080/?redirect=132.0.0.1')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['127.0.0.1'], 'http://127.0.0.1.appdomain.com/?redirect=127.0.0.1')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['127.0.0.1'], 'http://a127.0.0.1/?redirect=127.0.0.1')); - // Other schemes. - assertFalse( - fireauth.util.isAuthorizedDomain( - ['domain.com'], 'file://www.domain.com')); - assertFalse( - fireauth.util.isAuthorizedDomain( - ['domain.com'], 'other://www.domain.com')); -} - - -function testMatchDomain_chromeExtensionPattern() { - assertFalse(fireauth.util.matchDomain( - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456', - 'abcdefghijklmnopqrstuvwxyz123456', - 'http')); - assertFalse(fireauth.util.matchDomain( - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456', - 'abcdefghijklmnopqrstuvwxyz123456', - 'file')); - assertFalse(fireauth.util.matchDomain( - 'chrome-extension://prefix-abcdefghijklmnopqrstuvwxyz123456', - 'abcdefghijklmnopqrstuvwxyz123456', - 'chrome-extension')); - assertFalse(fireauth.util.matchDomain( - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456-suffix', - 'abcdefghijklmnopqrstuvwxyz123456', - 'chrome-extension')); - assertFalse(fireauth.util.matchDomain( - 'chrome-extension://prefix-abcdefghijklmnopqrstuvwxyz123456-suffix', - 'abcdefghijklmnopqrstuvwxyz123456', - 'chrome-extension')); - assertFalse(fireauth.util.matchDomain( - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456', - 'www.abcdefghijklmnopqrstuvwxyz123456', - 'chrome-extension')); - assertFalse(fireauth.util.matchDomain( - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456', - 'abcdefghijklmnopqrstuvwxyz123456.com', - 'chrome-extension')); - assertTrue(fireauth.util.matchDomain( - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456', - 'abcdefghijklmnopqrstuvwxyz123456', - 'chrome-extension')); - assertTrue(fireauth.util.matchDomain( - 'chrome-extension://abcdefghijklmnopqrstuvwxyz123456/popup.html', - 'abcdefghijklmnopqrstuvwxyz123456', - 'chrome-extension')); -} - - -function testMatchDomain_unsupportedScheme() { - assertFalse(fireauth.util.matchDomain('127.0.0.1', '127.0.0.1', 'file')); - assertFalse(fireauth.util.matchDomain('domain.com', 'domain.com', 'file')); -} - - -function testMatchDomain_ipAddressPattern() { - assertTrue(fireauth.util.matchDomain('127.0.0.1', '127.0.0.1', 'http')); - assertTrue(fireauth.util.matchDomain('127.0.0.1', '127.0.0.1', 'https')); - assertFalse(fireauth.util.matchDomain('127.0.0.1', 'a127.0.0.1', 'http')); - assertFalse(fireauth.util.matchDomain('127.0.0.1', 'abc.domain.com', 'http')); - assertFalse(fireauth.util.matchDomain( - '127.0.0.1', '127.0.0.1', 'chrome-extension')); -} - - -function testMatchDomain_ipAddressDomain() { - assertFalse(fireauth.util.matchDomain('domain.com', '127.0.0.1', 'http')); - assertFalse(fireauth.util.matchDomain('a127.0.0.1', '127.0.0.1', 'http')); -} - - -function testMatchDomain_caseInsensitiveMatch() { - assertTrue(fireauth.util.matchDomain('localhost', 'localhost', 'http')); - assertTrue(fireauth.util.matchDomain('domain.com', 'DOMAIN.COM', 'http')); - assertTrue(fireauth.util.matchDomain( - 'doMAin.com', 'abC.domain.COM', 'http')); - assertTrue(fireauth.util.matchDomain('localhost', 'localhost', 'https')); - assertTrue(fireauth.util.matchDomain('domain.com', 'DOMAIN.COM', 'https')); - assertTrue(fireauth.util.matchDomain( - 'doMAin.com', 'abC.domain.COM', 'https')); - assertFalse(fireauth.util.matchDomain( - 'doMAin.com', 'abC.domain.COM', 'chrome-extension')); -} - - -function testMatchDomain_domainMismatch() { - assertFalse(fireauth.util.matchDomain('domain.com', 'domain.com.lb', 'http')); - assertFalse(fireauth.util.matchDomain( - 'domain.com', 'abc.domain.com.lb', 'http')); - assertFalse(fireauth.util.matchDomain( - 'domain2.com', 'abc.domain.com', 'http')); -} - - -function testMatchDomain_subdomainComparison() { - assertTrue(fireauth.util.matchDomain('domain.com', 'abc.domain.com', 'http')); - assertTrue(fireauth.util.matchDomain( - 'domain.com', 'abc.domain.com', 'https')); - assertFalse(fireauth.util.matchDomain( - 'other.domain.com', 'abc.domain.com', 'http')); - assertTrue(fireauth.util.matchDomain( - 'domain.com', 'abc.ef.gh.domain.com', 'http')); - assertTrue(fireauth.util.matchDomain( - 'domain.com', 'abc.ef.gh.domain.com', 'https')); - assertFalse(fireauth.util.matchDomain( - 'domain.com', 'abc.ef.gh.domain.com', 'chrome-extension')); -} - - -function testMatchDomain_dotsInPatternEscaped() { - // Dots should be escaped. - assertFalse(fireauth.util.matchDomain( - 'domain.com', 'abc.domainacom', 'http')); - assertFalse(fireauth.util.matchDomain( - 'abc.def.com', 'abczdefzcom', 'http')); -} - - -function testIsValidEmailAddress() { - assertTrue(fireauth.util.isValidEmailAddress('test@abc.com')); - assertTrue(fireauth.util.isValidEmailAddress('abc@def')); - // International email addresses. - assertTrue(fireauth.util.isValidEmailAddress('Pelé@example.com')); - assertTrue(fireauth.util.isValidEmailAddress('我買@屋企.香港')); - // Invalid email addresses. - assertFalse(fireauth.util.isValidEmailAddress('abcdef')); - assertFalse(fireauth.util.isValidEmailAddress('abc.def')); - // Non-string. - assertFalse(fireauth.util.isValidEmailAddress(123)); - assertFalse(fireauth.util.isValidEmailAddress(null)); - assertFalse(fireauth.util.isValidEmailAddress(undefined)); -} - - -function testOnDomReady() { - return installAndRunTest('onDomReady', function() { - // Should resolve immediately. - return fireauth.util.onDomReady(); - }); -} - - -function testCreateStorageKey() { - assertEquals( - 'apiKey:appName', - fireauth.util.createStorageKey('apiKey', 'appName')); -} - - -function testGenerateRandomAlphaNumericString() { - // Confirm generated string has expected length. - for (var i = 0; i < 10; i++) { - assertEquals(i, fireauth.util.generateRandomAlphaNumericString(i).length); - } -} - - -function testGetEnvironment_browser() { - assertEquals(fireauth.util.Env.BROWSER, - fireauth.util.getEnvironment('Gecko')); -} - - -function testGetEnvironment_reactNative() { - stubs.set(firebase.INTERNAL, 'reactNative', {}); - assertEquals(fireauth.util.Env.REACT_NATIVE, - fireauth.util.getEnvironment()); -} - - -function testGetEnvironment_node() { - // Simulate Node.js environment. - stubs.set(firebase.INTERNAL, 'node', {}); - assertEquals(fireauth.util.Env.NODE, fireauth.util.getEnvironment()); -} - - -function testGetEnvironment_worker() { - // Simulate worker environment. - stubs.replace( - fireauth.util, - 'isWorker', - function() { - return true; - }); - assertEquals(fireauth.util.Env.WORKER, fireauth.util.getEnvironment()); -} - - -function testIsWorker() { - assertFalse(fireauth.util.isWorker({'window': {}})); - assertTrue(fireauth.util.isWorker({ - 'WorkerGlobalScope': function() {}, - 'importScripts': function() {} - })); -} - - -function testIsFetchSupported() { - // All fetch related APIs supported. - assertTrue(fireauth.util.isFetchSupported({ - 'fetch': function() {}, - 'Request': function() {}, - 'Headers': function() {} - })); - // Headers missing. - assertFalse(fireauth.util.isFetchSupported({ - 'fetch': function() {}, - 'Request': function() {}, - })); - // Request missing. - assertFalse(fireauth.util.isFetchSupported({ - 'fetch': function() {}, - 'Headers': function() {} - })); - // fetch missing. - assertFalse(fireauth.util.isFetchSupported({ - 'Request': function() {}, - 'Headers': function() {} - })); -} - - -function testGetBrowserName_opera() { - assertEquals('Opera', fireauth.util.getBrowserName(operaUA)); -} - - -function testGetBrowserName_ie() { - assertEquals('IE', fireauth.util.getBrowserName(ieUA)); -} - - -function testGetBrowserName_edge() { - assertEquals('Edge', fireauth.util.getBrowserName(edgeUA)); -} - - -function testGetBrowserName_firefox() { - assertEquals('Firefox', fireauth.util.getBrowserName(firefoxUA)); -} - - -function testGetBrowserName_silk() { - assertEquals('Silk', fireauth.util.getBrowserName(silkUA)); -} - - -function testGetBrowserName_safari() { - assertEquals('Safari', fireauth.util.getBrowserName(safariUA)); -} - - -function testGetBrowserName_chrome() { - assertEquals('Chrome', fireauth.util.getBrowserName(chromeUA)); -} - - -function testGetBrowserName_android() { - assertEquals('Android', fireauth.util.getBrowserName(androidUA)); -} - - -function testGetBrowserName_blackberry() { - assertEquals('Blackberry', fireauth.util.getBrowserName(blackberryUA)); -} - - -function testGetBrowserName_iemobile() { - assertEquals('IEMobile', fireauth.util.getBrowserName(windowsPhoneUA)); -} - - -function testGetBrowserName_webos() { - assertEquals('Webos', fireauth.util.getBrowserName(webOSUA)); -} - - -function testGetBrowserName_recognizable() { - var ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like ' + - 'Gecko) Awesome/2.0.012'; - assertEquals('Awesome', fireauth.util.getBrowserName(ua)); -} - - -function testGetBrowserName_other() { - var ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_2 like Mac OS X) AppleWebKi' + - 't/600.1.4 (KHTML, like Gecko) Mobile/12D508 [FBAN/FBIOS;FBAV/27.0.0.1' + - '0.12;FBBV/8291884;FBDV/iPhone7,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/8.2;' + - 'FBSS/3; FBCR/vodafoneIE;FBID/phone;FBLC/en_US;FBOP/5]'; - assertEquals('Other', fireauth.util.getBrowserName(ua)); -} - - -function testGetClientVersion() { - var ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like ' + - 'Gecko) Chrome/50.0.2661.94 Safari/537.36'; - var firebaseSdkVersion = '3.0.0'; - assertEquals( - 'Chrome/JsCore/3.0.0/FirebaseCore-web', - fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebaseSdkVersion, - null, ua)); -} - - -function testGetClientVersion_reactNative() { - stubs.set(firebase.INTERNAL, 'reactNative', {}); - var firebaseSdkVersion = '3.0.0'; - var navigatorProduct = 'ReactNative'; - var clientVersion = fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, - firebaseSdkVersion, - '', - navigatorProduct); - assertEquals('ReactNative/JsCore/3.0.0/FirebaseCore-web', clientVersion); -} - - -function testGetClientVersion_node() { - var firebaseSdkVersion = '3.0.0'; - // Simulate Node.js environment. - stubs.set(firebase.INTERNAL, 'node', {}); - var clientVersion = fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, - firebaseSdkVersion); - assertEquals('Node/JsCore/3.0.0/FirebaseCore-web', clientVersion); -} - - -function testGetClientVersion_worker() { - var ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like ' + - 'Gecko) Chrome/50.0.2661.94 Safari/537.36'; - var firebaseSdkVersion = '4.9.1'; - // Simulate worker environment. - stubs.replace( - fireauth.util, - 'isWorker', - function() { - return true; - }); - assertEquals( - 'Chrome-Worker/JsCore/4.9.1/FirebaseCore-web', - fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, firebaseSdkVersion, - null, ua)); -} - - -function testGetFrameworkIds() { - assertArrayEquals([], fireauth.util.getFrameworkIds([])); - assertArrayEquals([], fireauth.util.getFrameworkIds(['bla'])); - assertArrayEquals( - ['FirebaseUI-web'], fireauth.util.getFrameworkIds(['FirebaseUI-web'])); - assertArrayEquals( - ['FirebaseCore-web', 'FirebaseUI-web'], - fireauth.util.getFrameworkIds( - ['foo', 'FirebaseCore-web', 'bar', 'FirebaseCore-web', - 'FirebaseUI-web'])); - // Test frameworks IDs are sorted. - assertArrayEquals( - ['FirebaseCore-web', 'FirebaseUI-web'], - fireauth.util.getFrameworkIds(['FirebaseUI-web', 'FirebaseCore-web'])); -} - - -function testGetClientVersion_frameworkVersion_single() { - var ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like ' + - 'Gecko) Chrome/50.0.2661.94 Safari/537.36'; - var firebaseSdkVersion = '3.0.0'; - var clientVersion = fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, - firebaseSdkVersion, - ['FirebaseUI-web'], - ua); - assertEquals( - 'Chrome/JsCore/3.0.0/FirebaseUI-web', clientVersion); -} - - -function testGetClientVersion_frameworkVersion_multiple() { - var ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like ' + - 'Gecko) Chrome/50.0.2661.94 Safari/537.36'; - var firebaseSdkVersion = '3.0.0'; - var clientVersion = fireauth.util.getClientVersion( - fireauth.util.ClientImplementation.JSCORE, - firebaseSdkVersion, - ['foo', 'FirebaseCore-web', 'bar', 'FirebaseCore-web', 'FirebaseUI-web'], - ua); - assertEquals( - 'Chrome/JsCore/3.0.0/FirebaseCore-web,FirebaseUI-web', clientVersion); -} - - -function testGetObjectRef() { - var scope = { - 'a': false, - 'b': { - 'c': { - 'd': 123 - }, - 'e': null, - 'f': '', - 'g': 'hello', - 'h': true, - 'i': false, - 'j': undefined, - 'k': null - } - }; - assertUndefined(fireauth.util.getObjectRef('', scope)); - assertUndefined(fireauth.util.getObjectRef(' ', scope)); - assertUndefined(fireauth.util.getObjectRef('e', scope)); - assertUndefined(fireauth.util.getObjectRef('.a', scope)); - assertUndefined(fireauth.util.getObjectRef('a.', scope)); - assertEquals(false, fireauth.util.getObjectRef('a', scope)); - assertUndefined(fireauth.util.getObjectRef('a.b', scope)); - assertEquals(123, fireauth.util.getObjectRef('b.c.d', scope)); - assertNull(fireauth.util.getObjectRef('b.e', scope)); - assertEquals('', fireauth.util.getObjectRef('b.f', scope)); - assertEquals('hello', fireauth.util.getObjectRef('b.g', scope)); - assertEquals(true, fireauth.util.getObjectRef('b.h', scope)); - assertEquals(false, fireauth.util.getObjectRef('b.i', scope)); - assertUndefined(fireauth.util.getObjectRef('b.j', scope)); - assertNull(fireauth.util.getObjectRef('b.k', scope)); - assertUndefined(fireauth.util.getObjectRef('b.e.k', scope)); - assertObjectEquals({'d': 123}, fireauth.util.getObjectRef('b.c', scope)); -} - - -function testRunsInBackground_canRunInBackground() { - assertTrue(fireauth.util.runsInBackground(operaUA)); - assertTrue(fireauth.util.runsInBackground(ieUA)); - assertTrue(fireauth.util.runsInBackground(edgeUA)); - assertTrue(fireauth.util.runsInBackground(silkUA)); - assertTrue(fireauth.util.runsInBackground(safariUA)); - assertTrue(fireauth.util.runsInBackground(chromeUA)); -} - - -function testChromeVersion() { - // Should return null for non Chrome browsers. - assertNull(fireauth.util.getChromeVersion(operaUA)); - assertNull(fireauth.util.getChromeVersion(ieUA)); - assertNull(fireauth.util.getChromeVersion(edgeUA)); - assertNull(fireauth.util.getChromeVersion(firefoxUA)); - assertNull(fireauth.util.getChromeVersion(silkUA)); - assertNull(fireauth.util.getChromeVersion(safariUA)); - assertNull(fireauth.util.getChromeVersion(iOS8iPhoneUA)); - // Should return correct version for Chrome. - assertEquals(50, fireauth.util.getChromeVersion(chromeUA)); -} - - -function testRunsInBackground_cannotRunInBackground() { - assertFalse(fireauth.util.runsInBackground(iOS7iPodUA)); - assertFalse(fireauth.util.runsInBackground(iOS7iPhoneUA)); - assertFalse(fireauth.util.runsInBackground(iOS7iPadUA)); - assertFalse(fireauth.util.runsInBackground(iOS8iPhoneUA)); - assertFalse(fireauth.util.runsInBackground(iOS9iPhoneUA)); - assertFalse(fireauth.util.runsInBackground(firefoxUA)); - assertFalse(fireauth.util.runsInBackground(androidUA)); - assertFalse(fireauth.util.runsInBackground(blackberryUA)); - assertFalse(fireauth.util.runsInBackground(webOSUA)); - assertFalse(fireauth.util.runsInBackground(windowsPhoneUA)); -} - - -function testIsMobileBrowser() { - // Mobile OS. - assertTrue(fireauth.util.isMobileBrowser(iOS7iPodUA)); - assertTrue(fireauth.util.isMobileBrowser(iOS7iPhoneUA)); - assertTrue(fireauth.util.isMobileBrowser(iOS7iPadUA)); - assertTrue(fireauth.util.isMobileBrowser(iOS9iPhoneUA)); - assertTrue(fireauth.util.isMobileBrowser(iOS8iPhoneUA)); - assertTrue(fireauth.util.isMobileBrowser(androidUA)); - assertTrue(fireauth.util.isMobileBrowser(blackberryUA)); - assertTrue(fireauth.util.isMobileBrowser(webOSUA)); - assertTrue(fireauth.util.isMobileBrowser(windowsPhoneUA)); - // Desktop OS. - assertFalse(fireauth.util.isMobileBrowser(firefoxUA)); - assertFalse(fireauth.util.isMobileBrowser(operaUA)); - assertFalse(fireauth.util.isMobileBrowser(ieUA)); - assertFalse(fireauth.util.isMobileBrowser(edgeUA)); - assertFalse(fireauth.util.isMobileBrowser(firefoxUA)); - assertFalse(fireauth.util.isMobileBrowser(silkUA)); - assertFalse(fireauth.util.isMobileBrowser(safariUA)); -} - - -function testIframeCanSyncWebStorage() { - // Safari iOS. - assertFalse(fireauth.util.iframeCanSyncWebStorage(iOS7iPodUA)); - assertFalse(fireauth.util.iframeCanSyncWebStorage(iOS7iPhoneUA)); - assertFalse(fireauth.util.iframeCanSyncWebStorage(iOS7iPadUA)); - assertFalse(fireauth.util.iframeCanSyncWebStorage(iOS8iPhoneUA)); - // Desktop Safari. - assertFalse(fireauth.util.iframeCanSyncWebStorage(safariUA)); - // Chrome iOS. - assertFalse(fireauth.util.iframeCanSyncWebStorage(chriosUA)); - // Other Mobile OS. - assertTrue(fireauth.util.iframeCanSyncWebStorage(androidUA)); - assertTrue(fireauth.util.iframeCanSyncWebStorage(blackberryUA)); - assertTrue(fireauth.util.iframeCanSyncWebStorage(webOSUA)); - assertTrue(fireauth.util.iframeCanSyncWebStorage(windowsPhoneUA)); - // Desktop OS. - assertTrue(fireauth.util.iframeCanSyncWebStorage(firefoxUA)); - assertTrue(fireauth.util.iframeCanSyncWebStorage(operaUA)); - assertTrue(fireauth.util.iframeCanSyncWebStorage(ieUA)); - assertTrue(fireauth.util.iframeCanSyncWebStorage(edgeUA)); - assertTrue(fireauth.util.iframeCanSyncWebStorage(firefoxUA)); - assertTrue(fireauth.util.iframeCanSyncWebStorage(silkUA)); -} - - -function testStringifyJSON() { - assertObjectEquals(jsonString, fireauth.util.stringifyJSON(parsedJSON)); -} - - -function testStringifyJSON_undefined() { - assertNull(fireauth.util.stringifyJSON(undefined)); -} - - -function testParseJSON() { - assertObjectEquals(parsedJSON, fireauth.util.parseJSON(jsonString)); -} - - -function testParseJSON_null() { - assertUndefined(fireauth.util.parseJSON(null)); -} - - -function testParseJSON_noEval() { - stubs.replace(window, 'eval', function() { - throw 'eval() is not allowed in this context.'; - }); - assertObjectEquals(parsedJSON, fireauth.util.parseJSON(jsonString)); -} - - -function testParseJSON_syntaxError() { - assertThrows(function() { fireauth.util.parseJSON('{"a":2'); }); - assertThrows(function() { fireauth.util.parseJSON('b:"hello"}'); }); -} - - -function testGetWindowDimensions() { - var myWin = { - 'innerWidth': 1985.5, - 'innerHeight': 500.5 - }; - assertNull(fireauth.util.getWindowDimensions({})); - assertObjectEquals( - {'width': 1985.5, 'height': 500.5}, - fireauth.util.getWindowDimensions(myWin)); -} - - -function testIsPopupRedirectSupported_webStorageNotSupported() { - assertTrue(fireauth.util.isPopupRedirectSupported()); - stubs.replace(fireauth.util, 'isWebStorageSupported', function() { - return false; - }); - assertFalse(fireauth.util.isPopupRedirectSupported()); -} - - -function testIsPopupRedirectSupported_isAndroidOrIosCordovaScheme() { - fireauth.util.isCordovaEnabled = false; - assertTrue(fireauth.util.isPopupRedirectSupported()); - // Web storage supported. - stubs.replace(fireauth.util, 'isWebStorageSupported', function() { - return true; - }); - // File scheme. - stubs.replace(fireauth.util, 'getCurrentScheme', function() { - return 'file:'; - }); - // iOS or Android Cordova environment. - stubs.replace(fireauth.util, 'isAndroidOrIosCordovaScheme', function() { - return true; - }); - assertTrue(fireauth.util.isPopupRedirectSupported()); -} - - -function testIsPopupRedirectSupported_isChromeExtension() { - fireauth.util.isCordovaEnabled = false; - assertTrue(fireauth.util.isPopupRedirectSupported()); - // Web storage supported. - stubs.replace(fireauth.util, 'isWebStorageSupported', function() { - return true; - }); - // Chrome extension scheme. - stubs.replace(fireauth.util, 'getCurrentScheme', function() { - return 'chrome-extension:'; - }); - // Chrome extension. - stubs.replace(fireauth.util, 'isChromeExtension', function() { - return true; - }); - assertTrue(fireauth.util.isPopupRedirectSupported()); -} - - -function testIsPopupRedirectSupported_unsupportedFileEnvironment() { - fireauth.util.isCordovaEnabled = false; - assertTrue(fireauth.util.isPopupRedirectSupported()); - // Web storage supported. - stubs.replace(fireauth.util, 'isWebStorageSupported', function() { - return true; - }); - // File scheme. - stubs.replace(fireauth.util, 'getCurrentScheme', function() { - return 'file:'; - }); - // Neither iOS, nor Android Cordova environment. - stubs.replace(fireauth.util, 'isAndroidOrIosCordovaScheme', function() { - return false; - }); - assertFalse(fireauth.util.isPopupRedirectSupported()); -} - - -function testIsPopupRedirectSupported_unsupportedNativeEnvironment() { - fireauth.util.isCordovaEnabled = false; - assertTrue(fireauth.util.isPopupRedirectSupported()); - // Web storage supported. - stubs.replace(fireauth.util, 'isWebStorageSupported', function() { - return true; - }); - // https scheme. - stubs.replace(fireauth.util, 'getCurrentScheme', function() { - return 'https:'; - }); - // Neither iOS, nor Android Cordova environment. - stubs.replace(fireauth.util, 'isAndroidOrIosCordovaScheme', function() { - return false; - }); - // Native environment. - stubs.replace(fireauth.util, 'isNativeEnvironment', function() { - return true; - }); - assertFalse(fireauth.util.isPopupRedirectSupported()); -} - - -function testIsPopupRedirectSupported_workerEnvironment() { - fireauth.util.isCordovaEnabled = false; - // Web storage supported via indexedDB within worker. - stubs.replace(fireauth.util, 'isWebStorageSupported', function() { - return true; - }); - // HTTPS scheme. - stubs.replace(fireauth.util, 'getCurrentScheme', function() { - return 'https:'; - }); - // Neither iOS, nor Android Cordova environment. - stubs.replace(fireauth.util, 'isAndroidOrIosCordovaScheme', function() { - return false; - }); - // Non-native environment environment. - stubs.replace(fireauth.util, 'isNativeEnvironment', function() { - return false; - }); - // Popup/redirect should be supported with above conditions (minus worker). - assertTrue(fireauth.util.isPopupRedirectSupported()); - // Simulate worker environment. - stubs.replace(fireauth.util, 'isWorker', function() { - return true; - }); - // Popup/redirect no longer supported. - assertFalse(fireauth.util.isPopupRedirectSupported()); -} - - -function testIsChromeExtension() { - // Test https environment. - stubs.replace( - fireauth.util, - 'getCurrentScheme', - function() { - return 'https:'; - }); - assertFalse(fireauth.util.isChromeExtension()); - // Test Chrome extension environment. - stubs.replace( - fireauth.util, - 'getCurrentScheme', - function() { - return 'chrome-extension:'; - }); - assertTrue(fireauth.util.isChromeExtension()); -} - - -function testIsIOS() { - assertFalse(fireauth.util.isIOS(operaUA)); - assertFalse(fireauth.util.isIOS(ieUA)); - assertFalse(fireauth.util.isIOS(edgeUA)); - assertFalse(fireauth.util.isIOS(firefoxUA)); - assertFalse(fireauth.util.isIOS(silkUA)); - assertFalse(fireauth.util.isIOS(safariUA)); - assertFalse(fireauth.util.isIOS(chromeUA)); - assertFalse(fireauth.util.isIOS(androidUA)); - assertFalse(fireauth.util.isIOS(blackberryUA)); - assertFalse(fireauth.util.isIOS(webOSUA)); - assertFalse(fireauth.util.isIOS(windowsPhoneUA)); - assertTrue(fireauth.util.isIOS(iOS9iPhoneUA)); - assertTrue(fireauth.util.isIOS(iOS8iPhoneUA)); - assertTrue(fireauth.util.isIOS(iOS7iPodUA)); - assertTrue(fireauth.util.isIOS(iOS7iPadUA)); - assertTrue(fireauth.util.isIOS(iOS7iPhoneUA)); - assertTrue(fireauth.util.isIOS(chriosUA)); -} - - -function testIsAndroid() { - assertFalse(fireauth.util.isAndroid(operaUA)); - assertFalse(fireauth.util.isAndroid(ieUA)); - assertFalse(fireauth.util.isAndroid(edgeUA)); - assertFalse(fireauth.util.isAndroid(firefoxUA)); - assertFalse(fireauth.util.isAndroid(silkUA)); - assertFalse(fireauth.util.isAndroid(safariUA)); - assertFalse(fireauth.util.isAndroid(chromeUA)); - assertFalse(fireauth.util.isAndroid(blackberryUA)); - assertFalse(fireauth.util.isAndroid(webOSUA)); - assertFalse(fireauth.util.isAndroid(windowsPhoneUA)); - assertFalse(fireauth.util.isAndroid(iOS8iPhoneUA)); - assertFalse(fireauth.util.isAndroid(iOS7iPodUA)); - assertFalse(fireauth.util.isAndroid(iOS7iPadUA)); - assertFalse(fireauth.util.isAndroid(iOS7iPhoneUA)); - assertFalse(fireauth.util.isAndroid(iOS9iPhoneUA)); - assertFalse(fireauth.util.isAndroid(chriosUA)); - assertTrue(fireauth.util.isAndroid(androidUA)); -} - - -function testIsAndroidOrIosCodovaEnvironment() { - // Test https environment. - stubs.replace( - fireauth.util, - 'getCurrentScheme', - function() { - return 'https:'; - }); - // Non file environment. - assertFalse(fireauth.util.isAndroidOrIosCordovaScheme(iOS8iPhoneUA)); - // Test file environment. - stubs.replace( - fireauth.util, - 'getCurrentScheme', - function() { - return 'file:'; - }); - // iOS file environment. - assertTrue(fireauth.util.isAndroidOrIosCordovaScheme(iOS8iPhoneUA)); - // Android file environment. - assertTrue(fireauth.util.isAndroidOrIosCordovaScheme(androidUA)); - // Desktop file environment. - assertFalse(fireauth.util.isAndroidOrIosCordovaScheme(firefoxUA)); - // Test ionic environment. - stubs.replace( - fireauth.util, - 'getCurrentScheme', - function() { - return 'ionic:'; - }); - // iOS ionic environment. - assertTrue(fireauth.util.isAndroidOrIosCordovaScheme(iOS8iPhoneUA)); - // Android ionic environment. - assertTrue(fireauth.util.isAndroidOrIosCordovaScheme(androidUA)); - // Desktop ionic environment. - assertFalse(fireauth.util.isAndroidOrIosCordovaScheme(firefoxUA)); -} - - -function testIsIOS7Or8() { - assertTrue(fireauth.util.isIOS7Or8(iOS7iPodUA)); - assertTrue(fireauth.util.isIOS7Or8(iOS7iPhoneUA)); - assertTrue(fireauth.util.isIOS7Or8(iOS7iPadUA)); - assertTrue(fireauth.util.isIOS7Or8(iOS8iPhoneUA)); - assertFalse(fireauth.util.isIOS7Or8(iOS9iPhoneUA)); - assertFalse(fireauth.util.isIOS7Or8(firefoxUA)); - assertFalse(fireauth.util.isIOS7Or8(androidUA)); - assertFalse(fireauth.util.isIOS7Or8(blackberryUA)); - assertFalse(fireauth.util.isIOS7Or8(webOSUA)); - assertFalse(fireauth.util.isIOS7Or8(windowsPhoneUA)); -} - - -function testRequiresPopupDelay() { - assertFalse(fireauth.util.requiresPopupDelay(iOS7iPodUA)); - assertFalse(fireauth.util.requiresPopupDelay(iOS7iPhoneUA)); - assertFalse(fireauth.util.requiresPopupDelay(iOS7iPadUA)); - assertFalse(fireauth.util.requiresPopupDelay(iOS8iPhoneUA)); - assertFalse(fireauth.util.requiresPopupDelay(iOS9iPhoneUA)); - assertFalse(fireauth.util.requiresPopupDelay(firefoxUA)); - assertFalse(fireauth.util.requiresPopupDelay(androidUA)); - assertFalse(fireauth.util.requiresPopupDelay(blackberryUA)); - assertFalse(fireauth.util.requiresPopupDelay(webOSUA)); - assertFalse(fireauth.util.requiresPopupDelay(windowsPhoneUA)); - assertTrue(fireauth.util.requiresPopupDelay(chrome55iOS10UA)); -} - - -function testCheckIfCordova_incorrectFileEnvironment() { - return installAndRunTest('checkIfCordova_incorrectFileEnv', function() { - stubs.replace( - fireauth.util, - 'isAndroidOrIosCordovaScheme', - function() { - return false; - }); - return fireauth.util.checkIfCordova(null, 10).then(function() { - throw new Error('Unexpected success!'); - }).thenCatch(function(error) { - assertEquals( - 'Cordova must run in an Android or iOS file scheme.', - error.message); - }); - }); -} - - -function testCheckIfCordova_deviceReadyTimeout() { - return installAndRunTest('checkIfCordova_deviceReadyTimeout', function() { - stubs.replace( - fireauth.util, - 'isAndroidOrIosCordovaScheme', - function() { - return true; - }); - return fireauth.util.checkIfCordova(null, 10).then(function() { - throw new Error('Unexpected success!'); - }).thenCatch(function(error) { - assertEquals( - 'Cordova framework is not ready.', - error.message); - }); - }); -} - - -function testCheckIfCordova_success() { - return installAndRunTest('checkIfCordova_success', function() { - stubs.replace( - fireauth.util, - 'isAndroidOrIosCordovaScheme', - function() { - return true; - }); - var doc = goog.global.document; - // Create deviceready custom event. - var deviceReadyEvent = new CustomEvent('deviceready'); - var checkIfCordova = fireauth.util.checkIfCordova(null, 10); - // Trigger deviceready event on DOM ready. - fireauth.util.onDomReady().then(function() { - doc.dispatchEvent(deviceReadyEvent); - }); - return checkIfCordova; - }); -} - - -function testRemoveEntriesWithKeys() { - var obj = { - 'a': false, - 'b': undefined, - 'c': 'abc', - 'd': 1, - 'e': 0, - 'f': '', - 'g': 0.5, - 'h': null - }; - // Remove nothing from an empty object. - assertObjectEquals( - {}, - fireauth.util.removeEntriesWithKeys({}, [])); - - // Remove keys from an empty object. - assertObjectEquals( - {}, - fireauth.util.removeEntriesWithKeys({}, ['a', 'b'])); - - // Remove everything. - var filteredObj1 = {}; - var filter1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; - assertObjectEquals( - filteredObj1, - fireauth.util.removeEntriesWithKeys(obj, filter1)); - var filteredObj2 = obj; - - // Remove keys that do not exist. - var filter2 = ['i', 'j']; - assertObjectEquals( - filteredObj2, - fireauth.util.removeEntriesWithKeys(obj, filter2)); - - // Remove nothing. - assertObjectEquals( - filteredObj2, - fireauth.util.removeEntriesWithKeys(obj, [])); - - // Remove keys with values that are not true; if(obj[key]) resolves to false. - var filteredObj3 = { - 'c': 'abc', - 'd': 1, - 'g': 0.5 - }; - var filter3 = ['a', 'b', 'e', 'f', 'h', 'i', 'j']; - assertObjectEquals( - filteredObj3, - fireauth.util.removeEntriesWithKeys(obj, filter3)); - - // Keep keys with values that are not true. - var filteredObj4 = { - 'a': false, - 'b': undefined, - 'e': 0, - 'h': null - }; - var filter4 = ['c', 'd', 'f', 'g', 'i', 'j']; - assertObjectEquals( - filteredObj4, - fireauth.util.removeEntriesWithKeys(obj, filter4)); -} - - -function testCopyWithoutNullsOrUndefined() { - var obj = { - 'a': false, - 'b': undefined, - 'c': 'abc', - 'd': 1, - 'e': 0, - 'f': '', - 'g': 0.5, - 'h': null - }; - var filteredObj = { - 'a': false, - 'c': 'abc', - 'd': 1, - 'e': 0, - 'f': '', - 'g': 0.5, - }; - // All nulls and undefined removed. - assertObjectEquals( - filteredObj, - fireauth.util.copyWithoutNullsOrUndefined(obj)); - var obj2 = { - 'a': 1, - 'b': 2 - }; - // No nulls or undefined. - assertObjectEquals( - obj2, - fireauth.util.copyWithoutNullsOrUndefined(obj2)); - // Empty object. - assertObjectEquals( - {}, - fireauth.util.copyWithoutNullsOrUndefined({})); - // Object with undefined and nulls only. - assertObjectEquals( - {}, - fireauth.util.copyWithoutNullsOrUndefined({'b': undefined, 'c': null})); -} - - -function testIsMobileDevice() { - // Mobile devices. - assertTrue( - fireauth.util.isMobileDevice(chriosUA, fireauth.util.Env.BROWSER)); - assertTrue( - fireauth.util.isMobileDevice(null, fireauth.util.Env.REACT_NATIVE)); - // Desktop devices. - assertFalse( - fireauth.util.isMobileDevice(chromeUA, fireauth.util.Env.BROWSER)); - assertFalse( - fireauth.util.isMobileDevice(null, fireauth.util.Env.NODE)); - // For worker environments, the userAgent is still accessible and should be - // used to determine if the current device is a mobile device. - assertTrue( - fireauth.util.isMobileDevice(chriosUA, fireauth.util.Env.WORKER)); - assertFalse( - fireauth.util.isMobileDevice(chromeUA, fireauth.util.Env.WORKER)); -} - - -function testIsMobileDevice_desktopBrowser_default() { - // Simulate desktop browser. - stubs.replace(fireauth.util, 'isMobileBrowser', function(ua) { - return false; - }); - stubs.replace(fireauth.util, 'getEnvironment', function() { - return fireauth.util.Env.BROWSER; - }); - assertFalse(fireauth.util.isMobileDevice()); -} - - -function testIsMobileDevice_mobileBrowser_default() { - // Simulate mobile browser. - stubs.replace(fireauth.util, 'isMobileBrowser', function(ua) { - return true; - }); - stubs.replace(fireauth.util, 'getEnvironment', function() { - return fireauth.util.Env.BROWSER; - }); - assertTrue(fireauth.util.isMobileDevice()); -} - - -function testIsMobileDevice_desktopEnv_default() { - // Simulate desktop Node.js environment. - stubs.replace(fireauth.util, 'isMobileBrowser', function(ua) { - return false; - }); - stubs.replace(fireauth.util, 'getEnvironment', function() { - return fireauth.util.Env.NODE; - }); - assertFalse(fireauth.util.isMobileDevice()); -} - - -function testIsMobileDevice_mobileEnv_default() { - // Simulate mobile React Native environment. - stubs.replace(fireauth.util, 'isMobileBrowser', function(ua) { - return false; - }); - stubs.replace(fireauth.util, 'getEnvironment', function() { - return fireauth.util.Env.REACT_NATIVE; - }); - assertTrue(fireauth.util.isMobileDevice()); -} - - -function testIsMobileDevice_mobileWorker_default() { - // Simulate mobile browser. - stubs.replace(fireauth.util, 'isMobileBrowser', function(ua) { - return true; - }); - // Whether this is a worker or a non-worker shouldn't matter. - stubs.replace(fireauth.util, 'getEnvironment', function() { - return fireauth.util.Env.WORKER; - }); - assertTrue(fireauth.util.isMobileDevice()); -} - - -function testIsMobileDevice_desktopWorker_default() { - // Simulate desktop browser. - stubs.replace(fireauth.util, 'isMobileBrowser', function(ua) { - return false; - }); - // Whether this is a worker or a non-worker shouldn't matter. - stubs.replace(fireauth.util, 'getEnvironment', function() { - return fireauth.util.Env.WORKER; - }); - assertFalse(fireauth.util.isMobileDevice()); -} - - -function testIsOnline_httpOrHttps_online() { - // HTTP/HTTPS environment. - stubs.replace(fireauth.util, 'isHttpOrHttps', function(ua) { - return true; - }); - // Non-Chrome extension environment. - stubs.replace(fireauth.util, 'isChromeExtension', function() { - return false; - }); - // navigator.onLine resolves to true. - // fireauth.util.isOnline() should return true. - assertTrue(fireauth.util.isOnline({onLine: true})); -} - - -function testIsOnline_httpOrHttps_offline() { - // HTTP/HTTPS environment. - stubs.replace(fireauth.util, 'isHttpOrHttps', function(ua) { - return true; - }); - // Non-Chrome extension environment. - stubs.replace(fireauth.util, 'isChromeExtension', function() { - return false; - }); - // navigator.onLine resolves to false. - // fireauth.util.isOnline() should return false. - assertFalse(fireauth.util.isOnline({onLine: false})); -} - - -function testIsOnline_chromeExtension_online() { - // Non-HTTP/HTTPS environment. - stubs.replace(fireauth.util, 'isHttpOrHttps', function(ua) { - return false; - }); - // Chrome extension environment. - stubs.replace(fireauth.util, 'isChromeExtension', function() { - return true; - }); - // navigator.onLine resolves to true. - // fireauth.util.isOnline() should return true. - assertTrue(fireauth.util.isOnline({onLine: true})); -} - - -function testIsOnline_chromeExtension_offline() { - // Non-HTTP/HTTPS environment. - stubs.replace(fireauth.util, 'isHttpOrHttps', function(ua) { - return false; - }); - // Chrome extension environment. - stubs.replace(fireauth.util, 'isChromeExtension', function() { - return true; - }); - // navigator.onLine resolves to false. - // fireauth.util.isOnline() should return false. - assertFalse(fireauth.util.isOnline({onLine: false})); -} - - -function testIsOnline_other_navigatorConnection_online() { - // Non-HTTP/HTTPS environment. - stubs.replace(fireauth.util, 'isHttpOrHttps', function(ua) { - return false; - }); - // Non-Chrome extension environment. - stubs.replace(fireauth.util, 'isChromeExtension', function() { - return false; - }); - // cordova-plugin-network-information installed. - // navigator.onLine resolves to true. - // fireauth.util.isOnline() should return true. - assertTrue(fireauth.util.isOnline({onLine: true, connection: {}})); -} - - -function testIsOnline_other_navigatorConnection_offline() { - // Non-HTTP/HTTPS environment. - stubs.replace(fireauth.util, 'isHttpOrHttps', function(ua) { - return false; - }); - // Non-Chrome extension environment. - stubs.replace(fireauth.util, 'isChromeExtension', function() { - return false; - }); - // cordova-plugin-network-information installed. - // navigator.onLine resolves to false. - // fireauth.util.isOnline() should return false. - assertFalse(fireauth.util.isOnline({onLine: false, connection: {}})); -} - - -function testIsOnline_notSupported() { - // Non-HTTP/HTTPS environment. - stubs.replace(fireauth.util, 'isHttpOrHttps', function(ua) { - return false; - }); - // Non-Chrome extension environment. - stubs.replace(fireauth.util, 'isChromeExtension', function() { - return false; - }); - // Evaluates to true even though navigator.onLine is false. - assertTrue(fireauth.util.isOnline({onLine: false})); -} - - -function testDelay_invalid() { - assertThrows(function() { - new fireauth.util.Delay(50, 10); - }); -} - - -function testDelay_offline_defaultDelay() { - // Simulate navigator.onLine is false. - stubs.replace(fireauth.util, 'isOnline', function() { - return false; - }); - var delay = new fireauth.util.Delay(10000, 50000); - // Offline delay used instead of the supplied delay range. - assertEquals(5000, delay.get()); -} - - -function testDelay_offline_shortDelay() { - // Simulate navigator.onLine is false. - stubs.replace(fireauth.util, 'isOnline', function() { - return false; - }); - var delay = new fireauth.util.Delay(2000, 50000); - // Short delay used instead of offline delay. - assertEquals(2000, delay.get()); -} - - -function testDelay_mobileDevice_default() { - // Simulate mobile browser. - stubs.replace(fireauth.util, 'isMobileDevice', function(ua) { - return true; - }); - var delay = new fireauth.util.Delay(10, 50); - assertEquals(50, delay.get()); -} - - -function testDelay_desktopDevice_default() { - // Simulate desktop Node.js environment. - stubs.replace(fireauth.util, 'isMobileDevice', function(ua) { - return false; - }); - var delay = new fireauth.util.Delay(10, 50); - assertEquals(10, delay.get()); -} - - -function testDelay_desktopBrowser() { - var delay = - new fireauth.util.Delay(10, 50, chromeUA, fireauth.util.Env.BROWSER); - assertEquals(10, delay.get()); -} - - -function testDelay_mobileBrowser() { - var delay = - new fireauth.util.Delay(10, 50, chriosUA, fireauth.util.Env.BROWSER); - assertEquals(50, delay.get()); -} - - -function testDelay_desktopBrowser() { - // Whether this is a worker or a non-worker shouldn't matter. - // The userAgent is the authority on how the delay is determined. - var delay = - new fireauth.util.Delay(10, 50, chromeUA, fireauth.util.Env.WORKER); - assertEquals(10, delay.get()); -} - - -function testDelay_mobileBrowser() { - // Whether this is a worker or a non-worker shouldn't matter. - // The userAgent is the authority on how the delay is determined. - var delay = - new fireauth.util.Delay(10, 50, chriosUA, fireauth.util.Env.WORKER); - assertEquals(50, delay.get()); -} - - -function testDelay_node() { - var delay = new fireauth.util.Delay(10, 50, null, fireauth.util.Env.NODE); - assertEquals(10, delay.get()); -} - - -function testDelay_reactNative() { - stubs.set(firebase.INTERNAL, 'reactNative', {}); - var delay = - new fireauth.util.Delay(10, 50, null, fireauth.util.Env.REACT_NATIVE); - assertEquals(50, delay.get()); -} - - -function testGetUserLanguage() { - // Simulate modern browser. - assertEquals('de', fireauth.util.getUserLanguage({ - 'languages': ['de', 'en'], - 'language': 'en' - })); - - // Simulate non-Chrome/Firefox modern browser. - assertEquals('en', fireauth.util.getUserLanguage({ - 'language': 'en' - })); - - // Simulate older IE. - assertEquals('fr', fireauth.util.getUserLanguage({ - 'userLanguage': 'fr' - })); - - // Simulate other environment. - assertNull(fireauth.util.getUserLanguage({})); -} - - -function testIsIframe_iframe() { - // Mock window. - var win = { - name: 'windowA' - }; - // Set top window to another window. - win.top = { - name: 'windowB' - }; - // WindowB is the top window. - win.top.top = win.top; - assertTrue(fireauth.util.isIframe(win)); -} - - -function testIsIframe_notIframe() { - // Mock window. - var win = { - name: 'windowA' - }; - // Set top window to current window. - win.top = win; - assertFalse(fireauth.util.isIframe(win)); -} - - -function testIsOpenerAnIframe_openerIframe() { - // Simulate opener is an iframe. - // Mock opener window. - var win = { - name: 'windowA' - }; - // Set top window to another window. - win.top = { - name: 'windowB' - }; - // WindowB is the top window. - win.top.top = win.top; - // Current mock window with opener set to above window. - var currentWindow = { - name: 'windowC', - opener: win - }; - assertTrue(fireauth.util.isOpenerAnIframe(currentWindow)); -} - - -function testIsOpenerAnIframe_openerNotIframe() { - // Simulate opener is a top window. - // Mock window. - var win = { - name: 'windowA' - }; - // Set top window to current window. - win.top = win; - // Current mock window with opener set to above window. - var currentWindow = { - name: 'windowC', - opener: win - }; - assertFalse(fireauth.util.isOpenerAnIframe(currentWindow)); -} - - -function testIsOpenerAnIframe_noOpener() { - // Current window has no opener. - var currentWindow = { - name: 'windowC', - opener: null - }; - assertFalse(fireauth.util.isOpenerAnIframe(currentWindow)); -} - - -function testOnAppVisible_initiallyVisible() { - return installAndRunTest('onAppVisible_initiallyVisible', function() { - // Simulate app visible initially. - stubs.replace( - fireauth.util, - 'isAppVisible', - function() { - return true; - }); - // Should resolve quickly. - return fireauth.util.onAppVisible(); - }); -} - - -function testOnAppVisible_initiallyHidden() { - return installAndRunTest('onAppVisible_initiallyHidden', function() { - // Initially, simulate app not visible. - stubs.replace( - fireauth.util, - 'isAppVisible', - function() { - return false; - }); - // Reset event triggered flag. - var eventTriggered = false; - var doc = goog.global.document; - // Create custom visibility change event. - var visibilitychangeEvent = new CustomEvent('visibilitychange'); - // Run onAppVisible and save returned promise. - var p = fireauth.util.onAppVisible(); - // Simulate visibility change event after a short period of time. - setTimeout(function() { - // Simulate app visible after short period of time. - stubs.replace( - fireauth.util, - 'isAppVisible', - function() { - return true; - }); - // Trigger visibility change. - doc.dispatchEvent(visibilitychangeEvent); - // Track event being triggered. - eventTriggered = true; - }, 10); - return p.then(function() { - // Visibility change should have been triggered. - assertTrue(eventTriggered); - }); - }); -} - - -function testConsoleWarn() { - if (typeof console === 'undefined') { - // Ignore browsers that don't support console. The test - // testConsoleWarn_doesntBreakIE tests that this function doesn't break - // those browsers. - return; - } - const consoleWarn = mockControl.createMethodMock(goog.global.console, 'warn'); - const message = 'This is my message.'; - consoleWarn(message).$once(); - - mockControl.$replayAll(); - - fireauth.util.consoleWarn(message); -} - - -function testConsoleWarn_doesntBreakIE() { - fireauth.util.consoleWarn('This should not trigger an error in IE.'); -} - - -function testConsoleInfo() { - if (typeof console === 'undefined') { - // Ignore browsers that don't support console. The test - // testConsoleInfo_doesntBreakIE tests that this function doesn't break - // those browsers. - return; - } - const consoleInfo = mockControl.createMethodMock(goog.global.console, 'info'); - const message = 'This is my message.'; - consoleInfo(message).$once(); - - mockControl.$replayAll(); - - fireauth.util.consoleInfo(message); -} - - -function testConsoleInfo_doesntBreakIE() { - fireauth.util.consoleInfo('This should not trigger an error in IE.'); -} - - -function testUtcTimestampToDateString() { - var actual; - // Null. - assertNull(fireauth.util.utcTimestampToDateString(null)); - - // Invalid. - assertNull(fireauth.util.utcTimestampToDateString('bla')); - - // Null should be returned when a non numeric timestamp is provided. - assertNull( - fireauth.util.utcTimestampToDateString('22 Sep 2017 01:49:58 GMT')); - - // UTC timestamp string. - actual = fireauth.util.utcTimestampToDateString('1506044998000'); - assertTrue( - // All non IE10 browsers. - actual == 'Fri, 22 Sep 2017 01:49:58 GMT' || - // IE 10 browser. - actual == 'Fri, 22 Sep 2017 01:49:58 UTC'); - - // UTC timestamp number. - actual = fireauth.util.utcTimestampToDateString(1506046529000); - assertTrue( - // All non IE10 browsers. - actual == 'Fri, 22 Sep 2017 02:15:29 GMT' || - // IE 10 browser. - actual == 'Fri, 22 Sep 2017 02:15:29 UTC'); - assertEquals( - new Date(1506046529000).toUTCString(), - fireauth.util.utcTimestampToDateString(1506046529000)); -} - - -function testPersistsStorageWithIndexedDB() { - // SDK only: localStorage not synchronized and indexedDB available. - stubs.replace( - fireauth.util, - 'isAuthHandlerOrIframe', - function() {return false;}); - stubs.replace( - fireauth.util, - 'isLocalStorageNotSynchronized', - function() {return true;}); - stubs.replace( - fireauth.util, - 'isIndexedDBAvailable', - function() {return true;}); - assertTrue(fireauth.util.persistsStorageWithIndexedDB()); - - // SDK only: localStorage synchronized and indexedDB available. - stubs.replace( - fireauth.util, - 'isLocalStorageNotSynchronized', - function() {return false;}); - stubs.replace( - fireauth.util, - 'isIndexedDBAvailable', - function() {return true;}); - assertTrue(fireauth.util.persistsStorageWithIndexedDB()); - - // indexedDB not available. - stubs.reset(); - stubs.replace( - fireauth.util, - 'isIndexedDBAvailable', - function() {return false;}); - assertFalse(fireauth.util.persistsStorageWithIndexedDB()); - - // Auth handler/iframe: localStorage not synchronized and indexedDB available. - stubs.replace( - fireauth.util, - 'isAuthHandlerOrIframe', - function() {return true;}); - stubs.replace( - fireauth.util, - 'isLocalStorageNotSynchronized', - function() {return true;}); - stubs.replace( - fireauth.util, - 'isIndexedDBAvailable', - function() {return true;}); - assertTrue(fireauth.util.persistsStorageWithIndexedDB()); - - // Auth handler/iframe: localStorage synchronized and indexedDB available. - stubs.replace( - fireauth.util, - 'isAuthHandlerOrIframe', - function() {return true;}); - stubs.replace( - fireauth.util, - 'isLocalStorageNotSynchronized', - function() {return false;}); - stubs.replace( - fireauth.util, - 'isIndexedDBAvailable', - function() {return true;}); - assertFalse(fireauth.util.persistsStorageWithIndexedDB()); -} - - -/** - * @return {?HTMLElement} The last meta element if available in the head - * element. - */ -function getLastMetaTag() { - var collection = goog.dom.getElementsByTagName('head'); - if (collection.length) { - var metaTags = goog.dom.getElementsByTagName('meta', collection[0]); - if (metaTags.length > 0) { - return metaTags[metaTags.length - 1]; - } - } - return null; -} - - -function testSetNoReferrer() { - lastMetaTag = getLastMetaTag(); - if (lastMetaTag) { - assertNotEquals('referrer', lastMetaTag.getAttribute('name')); - } - fireauth.util.setNoReferrer(); - lastMetaTag = getLastMetaTag(); - assertEquals('referrer', lastMetaTag.getAttribute('name')); - assertEquals('no-referrer', lastMetaTag.getAttribute('content')); -} diff --git a/packages-exp/auth-exp/tsconfig.json b/packages/auth/tsconfig.json similarity index 100% rename from packages-exp/auth-exp/tsconfig.json rename to packages/auth/tsconfig.json diff --git a/packages/component/src/provider.test.ts b/packages/component/src/provider.test.ts index a6e7a076557..3f7e6c51023 100644 --- a/packages/component/src/provider.test.ts +++ b/packages/component/src/provider.test.ts @@ -20,7 +20,7 @@ import { fake, SinonSpy, match } from 'sinon'; import { ComponentContainer } from './component_container'; import { FirebaseService } from '@firebase/app-types/private'; // eslint-disable-next-line import/no-extraneous-dependencies -import { _FirebaseService } from '@firebase/app-exp'; +import { _FirebaseService } from '@firebase/app'; import { Provider } from './provider'; import { getFakeApp, getFakeComponent } from '../test/util'; import '../test/setup'; diff --git a/packages-exp/functions-exp/.eslintrc.js b/packages/database-compat/.eslintrc.js similarity index 53% rename from packages-exp/functions-exp/.eslintrc.js rename to packages/database-compat/.eslintrc.js index 11fa60d3e6a..3a04a986c41 100644 --- a/packages-exp/functions-exp/.eslintrc.js +++ b/packages/database-compat/.eslintrc.js @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ /** * @license * Copyright 2020 Google LLC @@ -15,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const path = require('path'); module.exports = { extends: '../../config/.eslintrc.js', @@ -26,12 +24,41 @@ module.exports = { tsconfigRootDir: __dirname }, rules: { - 'import/no-extraneous-dependencies': [ + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + 'no-restricted-properties': 'off', + 'no-restricted-globals': 'off', + 'no-throw-literal': 'off', + 'id-blacklist': 'off', + 'import/order': [ 'error', { - 'packageDir': [path.resolve(__dirname, '../../'), __dirname], - devDependencies: true + 'groups': [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index' + ], + 'newlines-between': 'always', + 'alphabetize': { 'order': 'asc', 'caseInsensitive': true } } ] - } + }, + overrides: [ + { + files: ['**/*.d.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off' + } + }, + { + files: ['scripts/*.ts'], + rules: { + 'import/no-extraneous-dependencies': 'off' + } + } + ] }; diff --git a/packages-exp/app-exp/README.md b/packages/database-compat/README.md similarity index 56% rename from packages-exp/app-exp/README.md rename to packages/database-compat/README.md index efe4a954881..656ab8397b9 100644 --- a/packages-exp/app-exp/README.md +++ b/packages/database-compat/README.md @@ -1,5 +1,5 @@ -# @firebase/app-exp +# @firebase/database-compat -This package coordinates the communication between the different Firebase components +This is the compatibility layer for the Firebase Realtime Database component of the Firebase JS SDK. **This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/functions-exp/karma.conf.js b/packages/database-compat/karma.conf.js similarity index 94% rename from packages-exp/functions-exp/karma.conf.js rename to packages/database-compat/karma.conf.js index d180371aeba..d51e08d046e 100644 --- a/packages-exp/functions-exp/karma.conf.js +++ b/packages/database-compat/karma.conf.js @@ -17,12 +17,12 @@ const karmaBase = require('../../config/karma.base'); -const files = [`src/**/*.test.ts`]; +const files = [`test/**/*.test.ts`]; module.exports = function (config) { const karmaConfig = Object.assign({}, karmaBase, { // files to load into karma - files, + files: files, // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['mocha'] diff --git a/packages/database-compat/package.json b/packages/database-compat/package.json new file mode 100644 index 00000000000..fc7c394e81e --- /dev/null +++ b/packages/database-compat/package.json @@ -0,0 +1,37 @@ +{ + "name": "@firebase/database-compat", + "version": "0.0.900", + "description": "The Realtime Database component of the Firebase JS SDK.", + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "esm5": "dist/index.esm5.js", + "license": "Apache-2.0", + "typings": "dist/database-compat/src/index.d.ts", + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "prettier": "prettier --write '*.js' '*.ts' '@(src|test)/**/*.ts'", + "build": "rollup -c rollup.config.js", + "build:release": "yarn build && yarn add-compat-overloads", + "build:deps": "lerna run --scope @firebase/database-compat --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p lint test:browser test:node", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test", + "test:browser": "karma start --single-run", + "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../database/dist/public.d.ts -o dist/database-compat/src/index.d.ts -a -r FirebaseDatabase:types.FirebaseDatabase -r Query:types.Query -r Reference:types.Reference -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/database" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "dependencies": { + "@firebase/database": "0.11.0", + "@firebase/database-types": "0.8.0", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.3.0", + "@firebase/component": "0.5.6", + "tslib": "^2.1.0" + } +} \ No newline at end of file diff --git a/packages/database/rollup.config.exp.js b/packages/database-compat/rollup.config.js similarity index 75% rename from packages/database/rollup.config.exp.js rename to packages/database-compat/rollup.config.js index bc913a6c987..14afce274e0 100644 --- a/packages/database/rollup.config.exp.js +++ b/packages/database-compat/rollup.config.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google LLC + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,16 +18,12 @@ import json from '@rollup/plugin-json'; import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; -import path from 'path'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import expPkg from './exp/package.json'; import pkg from './package.json'; -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; +const deps = Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) +); function onWarn(warning, defaultWarn) { if (warning.code === 'CIRCULAR_DEPENDENCY') { @@ -42,8 +38,7 @@ function onWarn(warning, defaultWarn) { const es5BuildPlugins = [ typescriptPlugin({ typescript, - abortOnError: false, - transformers: [importPathTransformer] + abortOnError: false }), json() ]; @@ -53,9 +48,13 @@ const es5Builds = [ * Node.js Build */ { - input: 'exp/index.node.ts', + input: 'src/index.node.ts', output: [ - { file: path.resolve('exp', expPkg.main), format: 'cjs', sourcemap: true } + { + file: pkg.main, + format: 'cjs', + sourcemap: true + } ], plugins: es5BuildPlugins, treeshake: { @@ -68,10 +67,10 @@ const es5Builds = [ * Browser Builds */ { - input: 'exp/index.ts', + input: 'src/index.ts', output: [ { - file: path.resolve('exp', expPkg.esm5), + file: pkg.esm5, format: 'es', sourcemap: true } @@ -96,8 +95,7 @@ const es2017BuildPlugins = [ target: 'es2017' } }, - abortOnError: false, - transformers: [importPathTransformer] + abortOnError: false }), json({ preferConst: true }) ]; @@ -107,10 +105,10 @@ const es2017Builds = [ * Browser Build */ { - input: 'exp/index.ts', + input: 'src/index.ts', output: [ { - file: path.resolve('exp', expPkg.browser), + file: pkg.browser, format: 'es', sourcemap: true } diff --git a/packages/database-compat/src/api/Database.ts b/packages/database-compat/src/api/Database.ts new file mode 100644 index 00000000000..a00e985219e --- /dev/null +++ b/packages/database-compat/src/api/Database.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// eslint-disable-next-line import/no-extraneous-dependencies + +import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseService } from '@firebase/app-types/private'; +import { + goOnline, + connectDatabaseEmulator, + goOffline, + ref, + refFromURL, + increment, + serverTimestamp, + Database as ModularDatabase +} from '@firebase/database'; +import { + validateArgCount, + Compat, + EmulatorMockTokenOptions +} from '@firebase/util'; + + +import { Reference } from './Reference'; + +/** + * Class representing a firebase database. + */ +export class Database implements FirebaseService, Compat { + static readonly ServerValue = { + TIMESTAMP: serverTimestamp(), + increment: (delta: number) => increment(delta) + }; + + /** + * The constructor should not be called by users of our public API. + */ + constructor(readonly _delegate: ModularDatabase, readonly app: FirebaseApp) {} + + INTERNAL = { + delete: () => this._delegate._delete() + }; + + /** + * Modify this instance to communicate with the Realtime Database emulator. + * + *

Note: This method must be called before performing any other operation. + * + * @param host - the emulator host (ex: localhost) + * @param port - the emulator port (ex: 8080) + * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules + */ + useEmulator( + host: string, + port: number, + options: { + mockUserToken?: EmulatorMockTokenOptions; + } = {} + ): void { + connectDatabaseEmulator(this._delegate, host, port, options); + } + + /** + * Returns a reference to the root or to the path specified in the provided + * argument. + * + * @param path - The relative string path or an existing Reference to a database + * location. + * @throws If a Reference is provided, throws if it does not belong to the + * same project. + * @returns Firebase reference. + */ + ref(path?: string): Reference; + ref(path?: Reference): Reference; + ref(path?: string | Reference): Reference { + validateArgCount('database.ref', 0, 1, arguments.length); + if (path instanceof Reference) { + const childRef = refFromURL(this._delegate, path.toString()); + return new Reference(this, childRef); + } else { + const childRef = ref(this._delegate, path); + return new Reference(this, childRef); + } + } + + /** + * Returns a reference to the root or the path specified in url. + * We throw a exception if the url is not in the same domain as the + * current repo. + * @returns Firebase reference. + */ + refFromURL(url: string): Reference { + const apiName = 'database.refFromURL'; + validateArgCount(apiName, 1, 1, arguments.length); + const childRef = refFromURL(this._delegate, url); + return new Reference(this, childRef); + } + + // Make individual repo go offline. + goOffline(): void { + validateArgCount('database.goOffline', 0, 0, arguments.length); + return goOffline(this._delegate); + } + + goOnline(): void { + validateArgCount('database.goOnline', 0, 0, arguments.length); + return goOnline(this._delegate); + } +} diff --git a/packages/database-compat/src/api/Reference.ts b/packages/database-compat/src/api/Reference.ts new file mode 100644 index 00000000000..e0ecfaee049 --- /dev/null +++ b/packages/database-compat/src/api/Reference.ts @@ -0,0 +1,790 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + OnDisconnect as ModularOnDisconnect, + off, + onChildAdded, + onChildChanged, + onChildMoved, + onChildRemoved, + onValue, + EventType, + limitToFirst, + query, + limitToLast, + orderByChild, + orderByKey, + orderByValue, + orderByPriority, + startAt, + startAfter, + endAt, + endBefore, + equalTo, + get, + set, + update, + setWithPriority, + remove, + setPriority, + push, + runTransaction, + child, + DataSnapshot as ModularDataSnapshot, + Query as ExpQuery, + DatabaseReference as ModularReference, + _QueryImpl, + _ReferenceImpl, + _validatePathString, + _validateWritablePath, + _UserCallback, + _QueryParams +} from '@firebase/database'; +import { + Compat, + Deferred, + errorPrefix, + validateArgCount, + validateCallback, + validateContextObject +} from '@firebase/util'; + +import { warn } from '../util/util'; +import { validateBoolean, validateEventType } from '../util/validation'; + +import { Database } from './Database'; +import { OnDisconnect } from './onDisconnect'; +import { TransactionResult } from './TransactionResult'; + +/** + * Class representing a firebase data snapshot. It wraps a SnapshotNode and + * surfaces the public methods (val, forEach, etc.) we want to expose. + */ +export class DataSnapshot implements Compat { + constructor( + readonly _database: Database, + readonly _delegate: ModularDataSnapshot + ) {} + + /** + * Retrieves the snapshot contents as JSON. Returns null if the snapshot is + * empty. + * + * @returns JSON representation of the DataSnapshot contents, or null if empty. + */ + val(): unknown { + validateArgCount('DataSnapshot.val', 0, 0, arguments.length); + return this._delegate.val(); + } + + /** + * Returns the snapshot contents as JSON, including priorities of node. Suitable for exporting + * the entire node contents. + * @returns JSON representation of the DataSnapshot contents, or null if empty. + */ + exportVal(): unknown { + validateArgCount('DataSnapshot.exportVal', 0, 0, arguments.length); + return this._delegate.exportVal(); + } + + // Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary + // for end-users + toJSON(): unknown { + // Optional spacer argument is unnecessary because we're depending on recursion rather than stringifying the content + validateArgCount('DataSnapshot.toJSON', 0, 1, arguments.length); + return this._delegate.toJSON(); + } + + /** + * Returns whether the snapshot contains a non-null value. + * + * @returns Whether the snapshot contains a non-null value, or is empty. + */ + exists(): boolean { + validateArgCount('DataSnapshot.exists', 0, 0, arguments.length); + return this._delegate.exists(); + } + + /** + * Returns a DataSnapshot of the specified child node's contents. + * + * @param path - Path to a child. + * @returns DataSnapshot for child node. + */ + child(path: string): DataSnapshot { + validateArgCount('DataSnapshot.child', 0, 1, arguments.length); + // Ensure the childPath is a string (can be a number) + path = String(path); + _validatePathString('DataSnapshot.child', 'path', path, false); + return new DataSnapshot(this._database, this._delegate.child(path)); + } + + /** + * Returns whether the snapshot contains a child at the specified path. + * + * @param path - Path to a child. + * @returns Whether the child exists. + */ + hasChild(path: string): boolean { + validateArgCount('DataSnapshot.hasChild', 1, 1, arguments.length); + _validatePathString('DataSnapshot.hasChild', 'path', path, false); + return this._delegate.hasChild(path); + } + + /** + * Returns the priority of the object, or null if no priority was set. + * + * @returns The priority. + */ + getPriority(): string | number | null { + validateArgCount('DataSnapshot.getPriority', 0, 0, arguments.length); + return this._delegate.priority; + } + + /** + * Iterates through child nodes and calls the specified action for each one. + * + * @param action - Callback function to be called + * for each child. + * @returns True if forEach was canceled by action returning true for + * one of the child nodes. + */ + forEach(action: (snapshot: DataSnapshot) => boolean | void): boolean { + validateArgCount('DataSnapshot.forEach', 1, 1, arguments.length); + validateCallback('DataSnapshot.forEach', 'action', action, false); + return this._delegate.forEach(expDataSnapshot => + action(new DataSnapshot(this._database, expDataSnapshot)) + ); + } + + /** + * Returns whether this DataSnapshot has children. + * @returns True if the DataSnapshot contains 1 or more child nodes. + */ + hasChildren(): boolean { + validateArgCount('DataSnapshot.hasChildren', 0, 0, arguments.length); + return this._delegate.hasChildren(); + } + + get key() { + return this._delegate.key; + } + + /** + * Returns the number of children for this DataSnapshot. + * @returns The number of children that this DataSnapshot contains. + */ + numChildren(): number { + validateArgCount('DataSnapshot.numChildren', 0, 0, arguments.length); + return this._delegate.size; + } + + /** + * @returns The Firebase reference for the location this snapshot's data came + * from. + */ + getRef(): Reference { + validateArgCount('DataSnapshot.ref', 0, 0, arguments.length); + return new Reference(this._database, this._delegate.ref); + } + + get ref(): Reference { + return this.getRef(); + } +} + +export interface SnapshotCallback { + (dataSnapshot: DataSnapshot, previousChildName?: string | null): unknown; +} + +/** + * A Query represents a filter to be applied to a firebase location. This object purely represents the + * query expression (and exposes our public API to build the query). The actual query logic is in ViewBase.js. + * + * Since every Firebase reference is a query, Firebase inherits from this object. + */ +export class Query implements Compat { + constructor(readonly database: Database, readonly _delegate: ExpQuery) {} + + on( + eventType: string, + callback: SnapshotCallback, + cancelCallbackOrContext?: ((a: Error) => unknown) | object | null, + context?: object | null + ): SnapshotCallback { + validateArgCount('Query.on', 2, 4, arguments.length); + validateCallback('Query.on', 'callback', callback, false); + + const ret = Query.getCancelAndContextArgs_( + 'Query.on', + cancelCallbackOrContext, + context + ); + const valueCallback = (expSnapshot, previousChildName?) => { + callback.call( + ret.context, + new DataSnapshot(this.database, expSnapshot), + previousChildName + ); + }; + valueCallback.userCallback = callback; + valueCallback.context = ret.context; + const cancelCallback = ret.cancel?.bind(ret.context); + + switch (eventType) { + case 'value': + onValue(this._delegate, valueCallback, cancelCallback); + return callback; + case 'child_added': + onChildAdded(this._delegate, valueCallback, cancelCallback); + return callback; + case 'child_removed': + onChildRemoved(this._delegate, valueCallback, cancelCallback); + return callback; + case 'child_changed': + onChildChanged(this._delegate, valueCallback, cancelCallback); + return callback; + case 'child_moved': + onChildMoved(this._delegate, valueCallback, cancelCallback); + return callback; + default: + throw new Error( + errorPrefix('Query.on', 'eventType') + + 'must be a valid event type = "value", "child_added", "child_removed", ' + + '"child_changed", or "child_moved".' + ); + } + } + + off( + eventType?: string, + callback?: SnapshotCallback, + context?: object | null + ): void { + validateArgCount('Query.off', 0, 3, arguments.length); + validateEventType('Query.off', eventType, true); + validateCallback('Query.off', 'callback', callback, true); + validateContextObject('Query.off', 'context', context, true); + if (callback) { + const valueCallback: _UserCallback = () => {}; + valueCallback.userCallback = callback; + valueCallback.context = context; + off(this._delegate, eventType as EventType, valueCallback); + } else { + off(this._delegate, eventType as EventType | undefined); + } + } + + /** + * Get the server-value for this query, or return a cached value if not connected. + */ + get(): Promise { + return get(this._delegate).then(expSnapshot => { + return new DataSnapshot(this.database, expSnapshot); + }); + } + + /** + * Attaches a listener, waits for the first event, and then removes the listener + */ + once( + eventType: string, + callback?: SnapshotCallback, + failureCallbackOrContext?: ((a: Error) => void) | object | null, + context?: object | null + ): Promise { + validateArgCount('Query.once', 1, 4, arguments.length); + validateCallback('Query.once', 'callback', callback, true); + + const ret = Query.getCancelAndContextArgs_( + 'Query.once', + failureCallbackOrContext, + context + ); + const deferred = new Deferred(); + const valueCallback: _UserCallback = (expSnapshot, previousChildName?) => { + const result = new DataSnapshot(this.database, expSnapshot); + if (callback) { + callback.call(ret.context, result, previousChildName); + } + deferred.resolve(result); + }; + valueCallback.userCallback = callback; + valueCallback.context = ret.context; + const cancelCallback = (error: Error) => { + if (ret.cancel) { + ret.cancel.call(ret.context, error); + } + deferred.reject(error); + }; + + switch (eventType) { + case 'value': + onValue(this._delegate, valueCallback, cancelCallback, { + onlyOnce: true + }); + break; + case 'child_added': + onChildAdded(this._delegate, valueCallback, cancelCallback, { + onlyOnce: true + }); + break; + case 'child_removed': + onChildRemoved(this._delegate, valueCallback, cancelCallback, { + onlyOnce: true + }); + break; + case 'child_changed': + onChildChanged(this._delegate, valueCallback, cancelCallback, { + onlyOnce: true + }); + break; + case 'child_moved': + onChildMoved(this._delegate, valueCallback, cancelCallback, { + onlyOnce: true + }); + break; + default: + throw new Error( + errorPrefix('Query.once', 'eventType') + + 'must be a valid event type = "value", "child_added", "child_removed", ' + + '"child_changed", or "child_moved".' + ); + } + + return deferred.promise; + } + + /** + * Set a limit and anchor it to the start of the window. + */ + limitToFirst(limit: number): Query { + validateArgCount('Query.limitToFirst', 1, 1, arguments.length); + return new Query(this.database, query(this._delegate, limitToFirst(limit))); + } + + /** + * Set a limit and anchor it to the end of the window. + */ + limitToLast(limit: number): Query { + validateArgCount('Query.limitToLast', 1, 1, arguments.length); + return new Query(this.database, query(this._delegate, limitToLast(limit))); + } + + /** + * Given a child path, return a new query ordered by the specified grandchild path. + */ + orderByChild(path: string): Query { + validateArgCount('Query.orderByChild', 1, 1, arguments.length); + return new Query(this.database, query(this._delegate, orderByChild(path))); + } + + /** + * Return a new query ordered by the KeyIndex + */ + orderByKey(): Query { + validateArgCount('Query.orderByKey', 0, 0, arguments.length); + return new Query(this.database, query(this._delegate, orderByKey())); + } + + /** + * Return a new query ordered by the PriorityIndex + */ + orderByPriority(): Query { + validateArgCount('Query.orderByPriority', 0, 0, arguments.length); + return new Query(this.database, query(this._delegate, orderByPriority())); + } + + /** + * Return a new query ordered by the ValueIndex + */ + orderByValue(): Query { + validateArgCount('Query.orderByValue', 0, 0, arguments.length); + return new Query(this.database, query(this._delegate, orderByValue())); + } + + startAt( + value: number | string | boolean | null = null, + name?: string | null + ): Query { + validateArgCount('Query.startAt', 0, 2, arguments.length); + return new Query( + this.database, + query(this._delegate, startAt(value, name)) + ); + } + + startAfter( + value: number | string | boolean | null = null, + name?: string | null + ): Query { + validateArgCount('Query.startAfter', 0, 2, arguments.length); + return new Query( + this.database, + query(this._delegate, startAfter(value, name)) + ); + } + + endAt( + value: number | string | boolean | null = null, + name?: string | null + ): Query { + validateArgCount('Query.endAt', 0, 2, arguments.length); + return new Query(this.database, query(this._delegate, endAt(value, name))); + } + + endBefore( + value: number | string | boolean | null = null, + name?: string | null + ): Query { + validateArgCount('Query.endBefore', 0, 2, arguments.length); + return new Query( + this.database, + query(this._delegate, endBefore(value, name)) + ); + } + + /** + * Load the selection of children with exactly the specified value, and, optionally, + * the specified name. + */ + equalTo(value: number | string | boolean | null, name?: string) { + validateArgCount('Query.equalTo', 1, 2, arguments.length); + return new Query( + this.database, + query(this._delegate, equalTo(value, name)) + ); + } + + /** + * @returns URL for this location. + */ + toString(): string { + validateArgCount('Query.toString', 0, 0, arguments.length); + return this._delegate.toString(); + } + + // Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary + // for end-users. + toJSON() { + // An optional spacer argument is unnecessary for a string. + validateArgCount('Query.toJSON', 0, 1, arguments.length); + return this._delegate.toJSON(); + } + + /** + * Return true if this query and the provided query are equivalent; otherwise, return false. + */ + isEqual(other: Query): boolean { + validateArgCount('Query.isEqual', 1, 1, arguments.length); + if (!(other instanceof Query)) { + const error = + 'Query.isEqual failed: First argument must be an instance of firebase.database.Query.'; + throw new Error(error); + } + return this._delegate.isEqual(other._delegate); + } + + /** + * Helper used by .on and .once to extract the context and or cancel arguments. + * @param fnName - The function name (on or once) + * + */ + private static getCancelAndContextArgs_( + fnName: string, + cancelOrContext?: ((a: Error) => void) | object | null, + context?: object | null + ): { cancel: ((a: Error) => void) | undefined; context: object | undefined } { + const ret: { + cancel: ((a: Error) => void) | null; + context: object | null; + } = { cancel: undefined, context: undefined }; + if (cancelOrContext && context) { + ret.cancel = cancelOrContext as (a: Error) => void; + validateCallback(fnName, 'cancel', ret.cancel, true); + + ret.context = context; + validateContextObject(fnName, 'context', ret.context, true); + } else if (cancelOrContext) { + // we have either a cancel callback or a context. + if (typeof cancelOrContext === 'object' && cancelOrContext !== null) { + // it's a context! + ret.context = cancelOrContext; + } else if (typeof cancelOrContext === 'function') { + ret.cancel = cancelOrContext as (a: Error) => void; + } else { + throw new Error( + errorPrefix(fnName, 'cancelOrContext') + + ' must either be a cancel callback or a context object.' + ); + } + } + return ret; + } + + get ref(): Reference { + return new Reference( + this.database, + new _ReferenceImpl(this._delegate._repo, this._delegate._path) + ); + } +} + +export class Reference extends Query implements Compat { + then: Promise['then']; + catch: Promise['catch']; + + /** + * Call options: + * new Reference(Repo, Path) or + * new Reference(url: string, string|RepoManager) + * + * Externally - this is the firebase.database.Reference type. + */ + constructor( + readonly database: Database, + readonly _delegate: ModularReference + ) { + super( + database, + new _QueryImpl( + _delegate._repo, + _delegate._path, + new _QueryParams(), + false + ) + ); + } + + /** @returns {?string} */ + getKey(): string | null { + validateArgCount('Reference.key', 0, 0, arguments.length); + return this._delegate.key; + } + + child(pathString: string): Reference { + validateArgCount('Reference.child', 1, 1, arguments.length); + if (typeof pathString === 'number') { + pathString = String(pathString); + } + return new Reference(this.database, child(this._delegate, pathString)); + } + + /** @returns {?Reference} */ + getParent(): Reference | null { + validateArgCount('Reference.parent', 0, 0, arguments.length); + const parent = this._delegate.parent; + return parent ? new Reference(this.database, parent) : null; + } + + /** @returns {!Reference} */ + getRoot(): Reference { + validateArgCount('Reference.root', 0, 0, arguments.length); + return new Reference(this.database, this._delegate.root); + } + + set( + newVal: unknown, + onComplete?: (error: Error | null) => void + ): Promise { + validateArgCount('Reference.set', 1, 2, arguments.length); + validateCallback('Reference.set', 'onComplete', onComplete, true); + const result = set(this._delegate, newVal); + if (onComplete) { + result.then( + () => onComplete(null), + error => onComplete(error) + ); + } + return result; + } + + update( + values: object, + onComplete?: (a: Error | null) => void + ): Promise { + validateArgCount('Reference.update', 1, 2, arguments.length); + + if (Array.isArray(values)) { + const newObjectToMerge: { [k: string]: unknown } = {}; + for (let i = 0; i < values.length; ++i) { + newObjectToMerge['' + i] = values[i]; + } + values = newObjectToMerge; + warn( + 'Passing an Array to Firebase.update() is deprecated. ' + + 'Use set() if you want to overwrite the existing data, or ' + + 'an Object with integer keys if you really do want to ' + + 'only update some of the children.' + ); + } + _validateWritablePath('Reference.update', this._delegate._path); + validateCallback('Reference.update', 'onComplete', onComplete, true); + + const result = update(this._delegate, values); + if (onComplete) { + result.then( + () => onComplete(null), + error => onComplete(error) + ); + } + return result; + } + + setWithPriority( + newVal: unknown, + newPriority: string | number | null, + onComplete?: (a: Error | null) => void + ): Promise { + validateArgCount('Reference.setWithPriority', 2, 3, arguments.length); + validateCallback( + 'Reference.setWithPriority', + 'onComplete', + onComplete, + true + ); + + const result = setWithPriority(this._delegate, newVal, newPriority); + if (onComplete) { + result.then( + () => onComplete(null), + error => onComplete(error) + ); + } + return result; + } + + remove(onComplete?: (a: Error | null) => void): Promise { + validateArgCount('Reference.remove', 0, 1, arguments.length); + validateCallback('Reference.remove', 'onComplete', onComplete, true); + + const result = remove(this._delegate); + if (onComplete) { + result.then( + () => onComplete(null), + error => onComplete(error) + ); + } + return result; + } + + transaction( + transactionUpdate: (currentData: unknown) => unknown, + onComplete?: ( + error: Error | null, + committed: boolean, + dataSnapshot: DataSnapshot | null + ) => void, + applyLocally?: boolean + ): Promise { + validateArgCount('Reference.transaction', 1, 3, arguments.length); + validateCallback( + 'Reference.transaction', + 'transactionUpdate', + transactionUpdate, + false + ); + validateCallback('Reference.transaction', 'onComplete', onComplete, true); + validateBoolean( + 'Reference.transaction', + 'applyLocally', + applyLocally, + true + ); + + const result = runTransaction(this._delegate, transactionUpdate, { + applyLocally + }).then( + transactionResult => + new TransactionResult( + transactionResult.committed, + new DataSnapshot(this.database, transactionResult.snapshot) + ) + ); + if (onComplete) { + result.then( + transactionResult => + onComplete( + null, + transactionResult.committed, + transactionResult.snapshot + ), + error => onComplete(error, false, null) + ); + } + return result; + } + + setPriority( + priority: string | number | null, + onComplete?: (a: Error | null) => void + ): Promise { + validateArgCount('Reference.setPriority', 1, 2, arguments.length); + validateCallback('Reference.setPriority', 'onComplete', onComplete, true); + + const result = setPriority(this._delegate, priority); + if (onComplete) { + result.then( + () => onComplete(null), + error => onComplete(error) + ); + } + return result; + } + + push(value?: unknown, onComplete?: (a: Error | null) => void): Reference { + validateArgCount('Reference.push', 0, 2, arguments.length); + validateCallback('Reference.push', 'onComplete', onComplete, true); + + const expPromise = push(this._delegate, value); + const promise = expPromise.then( + expRef => new Reference(this.database, expRef) + ); + + if (onComplete) { + promise.then( + () => onComplete(null), + error => onComplete(error) + ); + } + + const result = new Reference(this.database, expPromise); + result.then = promise.then.bind(promise); + result.catch = promise.catch.bind(promise, undefined); + return result; + } + + onDisconnect(): OnDisconnect { + _validateWritablePath('Reference.onDisconnect', this._delegate._path); + return new OnDisconnect( + new ModularOnDisconnect(this._delegate._repo, this._delegate._path) + ); + } + + get key(): string | null { + return this.getKey(); + } + + get parent(): Reference | null { + return this.getParent(); + } + + get root(): Reference { + return this.getRoot(); + } +} diff --git a/packages/database/src/api/TransactionResult.ts b/packages/database-compat/src/api/TransactionResult.ts similarity index 100% rename from packages/database/src/api/TransactionResult.ts rename to packages/database-compat/src/api/TransactionResult.ts diff --git a/packages/database/src/api/internal.ts b/packages/database-compat/src/api/internal.ts similarity index 55% rename from packages/database/src/api/internal.ts rename to packages/database-compat/src/api/internal.ts index ca3344d2440..6a8defcef7e 100644 --- a/packages/database/src/api/internal.ts +++ b/packages/database-compat/src/api/internal.ts @@ -26,68 +26,13 @@ import { ComponentType, Provider } from '@firebase/component'; -import * as types from '@firebase/database-types'; - -import { _repoManagerDatabaseFromApp } from '../../exp/index'; import { - repoInterceptServerData, - repoStats, - repoStatsIncrementCounter -} from '../core/Repo'; -import { setSDKVersion } from '../core/version'; -import { BrowserPollConnection } from '../realtime/BrowserPollConnection'; -import { WebSocketConnection } from '../realtime/WebSocketConnection'; + _repoManagerDatabaseFromApp, + _setSDKVersion +} from '@firebase/database'; +import * as types from '@firebase/database-types'; import { Database } from './Database'; -import { Reference } from './Reference'; - -/** - * INTERNAL methods for internal-use only (tests, etc.). - * - * Customers shouldn't use these or else should be aware that they could break at any time. - */ - -export const forceLongPolling = function () { - WebSocketConnection.forceDisallow(); - BrowserPollConnection.forceAllow(); -}; - -export const forceWebSockets = function () { - BrowserPollConnection.forceDisallow(); -}; - -/* Used by App Manager */ -export const isWebSocketsAvailable = function (): boolean { - return WebSocketConnection['isAvailable'](); -}; - -export const setSecurityDebugCallback = function ( - ref: Reference, - callback: (a: object) => void -) { - const connection = ref._delegate._repo.persistentConnection_; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (connection as any).securityDebugCallback_ = callback; -}; - -export const stats = function (ref: Reference, showDelta?: boolean) { - repoStats(ref._delegate._repo, showDelta); -}; - -export const statsIncrementCounter = function (ref: Reference, metric: string) { - repoStatsIncrementCounter(ref._delegate._repo, metric); -}; - -export const dataUpdateCount = function (ref: Reference): number { - return ref._delegate._repo.dataUpdateCount; -}; - -export const interceptServerData = function ( - ref: Reference, - callback: ((a: string, b: unknown) => void) | null -) { - return repoInterceptServerData(ref._delegate._repo, callback); -}; /** * Used by console to create a database based on the app, @@ -116,7 +61,7 @@ export function initStandalone({ instance: types.Database; namespace: T; } { - setSDKVersion(version); + _setSDKVersion(version); /** * ComponentContainer('database-standalone') is just a placeholder that doesn't perform diff --git a/packages/database/src/api/onDisconnect.ts b/packages/database-compat/src/api/onDisconnect.ts similarity index 85% rename from packages/database/src/api/onDisconnect.ts rename to packages/database-compat/src/api/onDisconnect.ts index 3a655f08337..577a8491029 100644 --- a/packages/database/src/api/onDisconnect.ts +++ b/packages/database-compat/src/api/onDisconnect.ts @@ -15,21 +15,12 @@ * limitations under the License. */ +import { OnDisconnect as ModularOnDisconnect } from '@firebase/database'; import { validateArgCount, validateCallback, Compat } from '@firebase/util'; -import { Indexable } from '../core/util/misc'; -import { warn } from '../core/util/util'; - -// TODO: revert to import { OnDisconnect as ExpOnDisconnect } from '../../exp/index'; once the modular SDK goes GA -/** - * This is a workaround for an issue in the no-modular '@firebase/database' where its typings - * reference types from `@firebase/app-exp`. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type ExpOnDisconnect = any; - -export class OnDisconnect implements Compat { - constructor(readonly _delegate: ExpOnDisconnect) {} +import { warn } from '../util/util'; +export class OnDisconnect implements Compat { + constructor(readonly _delegate: ModularOnDisconnect) {} cancel(onComplete?: (a: Error | null) => void): Promise { validateArgCount('OnDisconnect.cancel', 0, 1, arguments.length); @@ -93,7 +84,7 @@ export class OnDisconnect implements Compat { } update( - objectToMerge: Indexable, + objectToMerge: Record, onComplete?: (a: Error | null) => void ): Promise { validateArgCount('OnDisconnect.update', 1, 2, arguments.length); diff --git a/packages/database/compat/index.node.ts b/packages/database-compat/src/index.node.ts similarity index 88% rename from packages/database/compat/index.node.ts rename to packages/database-compat/src/index.node.ts index fcb89e54f8f..24e21de61c1 100644 --- a/packages/database/compat/index.node.ts +++ b/packages/database-compat/src/index.node.ts @@ -19,21 +19,14 @@ import { FirebaseApp, FirebaseNamespace } from '@firebase/app-types'; import { _FirebaseNamespace } from '@firebase/app-types/private'; import { FirebaseAuthInternal } from '@firebase/auth-interop-types'; import { Component, ComponentType } from '@firebase/component'; +import { enableLogging } from '@firebase/database'; import * as types from '@firebase/database-types'; import { CONSTANTS, isNodeSdk } from '@firebase/util'; -import { Client } from 'faye-websocket'; -import { enableLogging } from '../exp/index'; +import { name, version } from '../package.json'; import { Database } from '../src/api/Database'; import * as INTERNAL from '../src/api/internal'; import { DataSnapshot, Query, Reference } from '../src/api/Reference'; -import * as TEST_ACCESS from '../src/api/test_access'; -import { setSDKVersion } from '../src/core/version'; -import { setWebSocketImpl } from '../src/realtime/WebSocketConnection'; - -import { name, version } from './package.json'; - -setWebSocketImpl(Client); const ServerValue = Database.ServerValue; @@ -67,17 +60,13 @@ export function initStandalone( DataSnapshot, enableLogging, INTERNAL, - ServerValue, - TEST_ACCESS + ServerValue }, nodeAdmin }); } export function registerDatabase(instance: FirebaseNamespace) { - // set SDK_VERSION - setSDKVersion(instance.SDK_VERSION); - // Register the Database Service with the 'firebase' namespace. const namespace = (instance as _FirebaseNamespace).INTERNAL.registerComponent( new Component( @@ -87,7 +76,7 @@ export function registerDatabase(instance: FirebaseNamespace) { // getImmediate for FirebaseApp will always succeed const app = container.getProvider('app-compat').getImmediate(); const databaseExp = container - .getProvider('database-exp') + .getProvider('database') .getImmediate({ identifier: url }); return new Database(databaseExp, app); }, @@ -102,8 +91,7 @@ export function registerDatabase(instance: FirebaseNamespace) { DataSnapshot, enableLogging, INTERNAL, - ServerValue, - TEST_ACCESS + ServerValue } ) .setMultipleInstances(true) @@ -136,7 +124,7 @@ try { // Types to export for the admin SDK export { Database, Query, Reference, enableLogging, ServerValue }; -export { OnDisconnect } from '../src/api/onDisconnect'; +export { OnDisconnect } from '@firebase/database'; declare module '@firebase/app-compat' { interface FirebaseNamespace { diff --git a/packages/database/compat/index.ts b/packages/database-compat/src/index.ts similarity index 81% rename from packages/database/compat/index.ts rename to packages/database-compat/src/index.ts index a254c07864f..5ea3d61c083 100644 --- a/packages/database/compat/index.ts +++ b/packages/database-compat/src/index.ts @@ -19,31 +19,21 @@ import firebase, { FirebaseNamespace } from '@firebase/app-compat'; import { _FirebaseNamespace } from '@firebase/app-types/private'; import { Component, ComponentType } from '@firebase/component'; +import { enableLogging } from '@firebase/database'; import * as types from '@firebase/database-types'; -import { enableLogging } from '../exp/index'; +import { name, version } from '../package.json'; import { Database } from '../src/api/Database'; import * as INTERNAL from '../src/api/internal'; import { DataSnapshot, Query, Reference } from '../src/api/Reference'; -import * as TEST_ACCESS from '../src/api/test_access'; -import { setSDKVersion } from '../src/core/version'; - -import { name, version } from './package.json'; - -declare module '@firebase/component' { - interface NameServiceMapping { - 'database-compat': Database; - } -} const ServerValue = Database.ServerValue; export function registerDatabase(instance: FirebaseNamespace) { - // set SDK_VERSION - setSDKVersion(instance.SDK_VERSION); - // Register the Database Service with the 'firebase' namespace. - const namespace = ((instance as unknown) as _FirebaseNamespace).INTERNAL.registerComponent( + const namespace = ( + instance as unknown as _FirebaseNamespace + ).INTERNAL.registerComponent( new Component( 'database-compat', (container, { instanceIdentifier: url }) => { @@ -51,7 +41,7 @@ export function registerDatabase(instance: FirebaseNamespace) { // getImmediate for FirebaseApp will always succeed const app = container.getProvider('app-compat').getImmediate(); const databaseExp = container - .getProvider('database-exp') + .getProvider('database') .getImmediate({ identifier: url }); return new Database(databaseExp, app); }, @@ -66,8 +56,7 @@ export function registerDatabase(instance: FirebaseNamespace) { DataSnapshot, enableLogging, INTERNAL, - ServerValue, - TEST_ACCESS + ServerValue } ) .setMultipleInstances(true) diff --git a/packages/database-compat/src/util/util.ts b/packages/database-compat/src/util/util.ts new file mode 100644 index 00000000000..34e691f8a43 --- /dev/null +++ b/packages/database-compat/src/util/util.ts @@ -0,0 +1,8 @@ +import { Logger } from '@firebase/logger'; + +const logClient = new Logger('@firebase/database-compat'); + +export const warn = function (msg: string) { + const message = 'FIREBASE WARNING: ' + msg; + logClient.warn(message); +}; diff --git a/packages/database-compat/src/util/validation.ts b/packages/database-compat/src/util/validation.ts new file mode 100644 index 00000000000..56eeaeabd04 --- /dev/null +++ b/packages/database-compat/src/util/validation.ts @@ -0,0 +1,42 @@ +import { errorPrefix as errorPrefixFxn } from '@firebase/util'; + +export const validateBoolean = function ( + fnName: string, + argumentName: string, + bool: unknown, + optional: boolean +) { + if (optional && bool === undefined) { + return; + } + if (typeof bool !== 'boolean') { + throw new Error( + errorPrefixFxn(fnName, argumentName) + 'must be a boolean.' + ); + } +}; + +export const validateEventType = function ( + fnName: string, + eventType: string, + optional: boolean +) { + if (optional && eventType === undefined) { + return; + } + + switch (eventType) { + case 'value': + case 'child_added': + case 'child_removed': + case 'child_changed': + case 'child_moved': + break; + default: + throw new Error( + errorPrefixFxn(fnName, 'eventType') + + 'must be a valid event type = "value", "child_added", "child_removed", ' + + '"child_changed", or "child_moved".' + ); + } +}; diff --git a/packages/database/test/browser/crawler_support.test.ts b/packages/database-compat/test/browser/crawler_support.test.ts similarity index 98% rename from packages/database/test/browser/crawler_support.test.ts rename to packages/database-compat/test/browser/crawler_support.test.ts index 8b20caaebff..417845253fa 100644 --- a/packages/database/test/browser/crawler_support.test.ts +++ b/packages/database-compat/test/browser/crawler_support.test.ts @@ -15,9 +15,9 @@ * limitations under the License. */ +import { _TEST_ACCESS_forceRestClient as forceRestClient } from '@firebase/database'; import { expect } from 'chai'; -import { forceRestClient } from '../../src/api/test_access'; import { getRandomNode, getFreshRepoFromReference } from '../helpers/util'; // Some sanity checks for the ReadonlyRestClient crawler support. diff --git a/packages/database/test/database.test.ts b/packages/database-compat/test/database.test.ts similarity index 99% rename from packages/database/test/database.test.ts rename to packages/database-compat/test/database.test.ts index e89d98e373f..e72d7e53c34 100644 --- a/packages/database/test/database.test.ts +++ b/packages/database-compat/test/database.test.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import firebase from '@firebase/app'; +import firebase from '@firebase/app-compat'; import { expect } from 'chai'; import { DATABASE_ADDRESS, createTestApp } from './helpers/util'; -import '../index'; +import '../src/index'; describe('Database Tests', () => { let defaultApp; diff --git a/packages/database/test/datasnapshot.test.ts b/packages/database-compat/test/datasnapshot.test.ts similarity index 97% rename from packages/database/test/datasnapshot.test.ts rename to packages/database-compat/test/datasnapshot.test.ts index d6114b39ff1..50ee7f79f1f 100644 --- a/packages/database/test/datasnapshot.test.ts +++ b/packages/database-compat/test/datasnapshot.test.ts @@ -15,12 +15,13 @@ * limitations under the License. */ + +import { DataSnapshot as ExpDataSnapshot } from '@firebase/database'; import { expect } from 'chai'; +import { PRIORITY_INDEX } from '../../database/src/core/snap/indexes/PriorityIndex'; +import { nodeFromJSON } from '../../database/src/core/snap/nodeFromJSON'; import { DataSnapshot, Reference } from '../src/api/Reference'; -import { PRIORITY_INDEX } from '../src/core/snap/indexes/PriorityIndex'; -import { nodeFromJSON } from '../src/core/snap/nodeFromJSON'; -import { DataSnapshot as ExpDataSnapshot } from '../src/exp/Reference_impl'; import { getRandomNode } from './helpers/util'; diff --git a/packages/database/test/helpers/events.ts b/packages/database-compat/test/helpers/events.ts similarity index 99% rename from packages/database/test/helpers/events.ts rename to packages/database-compat/test/helpers/events.ts index 144ea2b10d5..d50ab5b7b7c 100644 --- a/packages/database/test/helpers/events.ts +++ b/packages/database-compat/test/helpers/events.ts @@ -15,8 +15,8 @@ * limitations under the License. */ +import { pathParent } from '../../../database/src/core/util/Path'; import { Reference } from '../../src/api/Reference'; -import { pathParent } from '../../src/core/util/Path'; import { TEST_PROJECT } from './util'; diff --git a/packages/database-compat/test/helpers/util.ts b/packages/database-compat/test/helpers/util.ts new file mode 100644 index 00000000000..a932c929843 --- /dev/null +++ b/packages/database-compat/test/helpers/util.ts @@ -0,0 +1,177 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare let MozWebSocket: WebSocket; + +import '../../src/index'; + +import firebase from '@firebase/app-compat'; +import { _FirebaseNamespace } from '@firebase/app-types/private'; +import { Component, ComponentType } from '@firebase/component'; + +import { Path } from '../../../database/src/core/util/Path'; +import { Query, Reference } from '../../src/api/Reference'; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +export const TEST_PROJECT = require('../../../../config/project.json'); + +const EMULATOR_PORT = process.env.RTDB_EMULATOR_PORT; +const EMULATOR_NAMESPACE = process.env.RTDB_EMULATOR_NAMESPACE; + +const USE_EMULATOR = !!EMULATOR_PORT; + +/* + * When running against the emulator, the hostname will be "localhost" rather + * than ".firebaseio.com", and so we need to append the namespace + * as a query param. + * + * Some tests look for hostname only while others need full url (with the + * namespace provided as a query param), hence below declarations. + */ +export const DATABASE_ADDRESS = USE_EMULATOR + ? `http://localhost:${EMULATOR_PORT}` + : TEST_PROJECT.databaseURL; + +export const DATABASE_URL = USE_EMULATOR + ? `${DATABASE_ADDRESS}?ns=${EMULATOR_NAMESPACE}` + : TEST_PROJECT.databaseURL; + +console.log(`USE_EMULATOR: ${USE_EMULATOR}. DATABASE_URL: ${DATABASE_URL}.`); + +let numDatabases = 0; + +// mock authentication functions for testing +(firebase as unknown as _FirebaseNamespace).INTERNAL.registerComponent( + new Component( + 'auth-internal', + () => ({ + getToken: async () => null, + addAuthTokenListener: () => {}, + removeAuthTokenListener: () => {}, + getUid: () => null + }), + ComponentType.PRIVATE + ) +); + +export function createTestApp() { + const app = firebase.initializeApp({ databaseURL: DATABASE_URL }); + return app; +} + +/** + * Gets or creates a root node to the test namespace. All calls sharing the + * value of opt_i will share an app context. + */ +export function getRootNode(i = 0, ref?: string) { + if (i + 1 > numDatabases) { + numDatabases = i + 1; + } + let app; + try { + app = firebase.app('TEST-' + i); + } catch (e) { + app = firebase.initializeApp({ databaseURL: DATABASE_URL }, 'TEST-' + i); + } + const db = app.database(); + return db.ref(ref); +} + +/** + * Create multiple refs to the same top level + * push key - each on it's own Firebase.Context. + */ +export function getRandomNode(numNodes?): Reference | Reference[] { + if (numNodes === undefined) { + return getRandomNode(1)[0] as Reference; + } + + let child; + const nodeList = []; + for (let i = 0; i < numNodes; i++) { + const ref = getRootNode(i); + if (child === undefined) { + child = ref.push().key; + } + + nodeList[i] = ref.child(child); + } + + return nodeList as Reference[]; +} + +export function getQueryValue(query: Query) { + return query.once('value').then(snap => snap.val()); +} + +export function pause(milliseconds: number) { + return new Promise(resolve => { + setTimeout(() => resolve(), milliseconds); + }); +} + +export function getPath(query: Query) { + return query.toString().replace(DATABASE_ADDRESS, ''); +} + +export function shuffle(arr, randFn = Math.random) { + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(randFn() * (i + 1)); + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } +} + +let freshRepoId = 1; +const activeFreshApps = []; + +export function getFreshRepo(path: Path) { + const app = firebase.initializeApp( + { databaseURL: DATABASE_URL }, + 'ISOLATED_REPO_' + freshRepoId++ + ); + activeFreshApps.push(app); + return (app as any).database().ref(path.toString()); +} + +export function getFreshRepoFromReference(ref) { + const host = ref.root.toString(); + const path = ref.toString().replace(host, ''); + return getFreshRepo(path); +} + +// Little helpers to get the currently cached snapshot / value. +export function getSnap(path) { + let snap; + const callback = function (snapshot) { + snap = snapshot; + }; + path.once('value', callback); + return snap; +} + +export function getVal(path) { + const snap = getSnap(path); + return snap ? snap.val() : undefined; +} + +export function canCreateExtraConnections() { + return ( + typeof MozWebSocket !== 'undefined' || typeof WebSocket !== 'undefined' + ); +} diff --git a/packages/database/test/info.test.ts b/packages/database-compat/test/info.test.ts similarity index 98% rename from packages/database/test/info.test.ts rename to packages/database-compat/test/info.test.ts index 0bea5859b37..45afd66f90e 100644 --- a/packages/database/test/info.test.ts +++ b/packages/database-compat/test/info.test.ts @@ -17,9 +17,9 @@ import { expect } from 'chai'; +import { EventAccumulator } from '../../database/test/helpers/EventAccumulator'; import { Reference } from '../src/api/Reference'; -import { EventAccumulator } from './helpers/EventAccumulator'; import { getFreshRepo, getRootNode, diff --git a/packages/database/test/order.test.ts b/packages/database-compat/test/order.test.ts similarity index 99% rename from packages/database/test/order.test.ts rename to packages/database-compat/test/order.test.ts index 6d0957271e3..913f61c6a1c 100644 --- a/packages/database/test/order.test.ts +++ b/packages/database-compat/test/order.test.ts @@ -17,9 +17,9 @@ import { expect } from 'chai'; +import { EventAccumulator } from '../../database/test/helpers/EventAccumulator'; import { Reference } from '../src/api/Reference'; -import { EventAccumulator } from './helpers/EventAccumulator'; import { eventTestHelper } from './helpers/events'; import { getRandomNode } from './helpers/util'; diff --git a/packages/database/test/order_by.test.ts b/packages/database-compat/test/order_by.test.ts similarity index 99% rename from packages/database/test/order_by.test.ts rename to packages/database-compat/test/order_by.test.ts index b9f676e8bbe..75cb40ff21f 100644 --- a/packages/database/test/order_by.test.ts +++ b/packages/database-compat/test/order_by.test.ts @@ -17,9 +17,9 @@ import { expect } from 'chai'; +import { EventAccumulatorFactory } from '../../database/test/helpers/EventAccumulator'; import { Reference } from '../src/api/Reference'; -import { EventAccumulatorFactory } from './helpers/EventAccumulator'; import { getRandomNode } from './helpers/util'; describe('.orderBy tests', () => { diff --git a/packages/database/test/promise.test.ts b/packages/database-compat/test/promise.test.ts similarity index 100% rename from packages/database/test/promise.test.ts rename to packages/database-compat/test/promise.test.ts diff --git a/packages/database/test/query.test.ts b/packages/database-compat/test/query.test.ts similarity index 99% rename from packages/database/test/query.test.ts rename to packages/database-compat/test/query.test.ts index 87f97a65e41..4a82c295b56 100644 --- a/packages/database/test/query.test.ts +++ b/packages/database-compat/test/query.test.ts @@ -19,13 +19,16 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as _ from 'lodash'; -import { DataSnapshot, Query, Reference } from '../src/api/Reference'; -import { INTEGER_32_MAX, INTEGER_32_MIN } from '../src/core/util/util'; - +import { + INTEGER_32_MAX, + INTEGER_32_MIN +} from '../../database/src/core/util/util'; import { EventAccumulator, EventAccumulatorFactory -} from './helpers/EventAccumulator'; +} from '../../database/test/helpers/EventAccumulator'; +import { DataSnapshot, Query, Reference } from '../src/api/Reference'; + import { getFreshRepo, getPath, getRandomNode, pause } from './helpers/util'; use(chaiAsPromised); @@ -2511,8 +2514,9 @@ describe('Query Tests', () => { }); function dumpListens(node: Query) { - const listens: Map> = (node._delegate._repo - .persistentConnection_ as any).listens; + const listens: Map> = ( + node._delegate._repo.persistentConnection_ as any + ).listens; const nodePath = getPath(node); const listenPaths = []; for (const path of listens.keys()) { diff --git a/packages/database/test/servervalues.test.ts b/packages/database-compat/test/servervalues.test.ts similarity index 100% rename from packages/database/test/servervalues.test.ts rename to packages/database-compat/test/servervalues.test.ts diff --git a/packages/database/test/transaction.test.ts b/packages/database-compat/test/transaction.test.ts similarity index 99% rename from packages/database/test/transaction.test.ts rename to packages/database-compat/test/transaction.test.ts index 5bf5caabd2b..5ede5ff22f0 100644 --- a/packages/database/test/transaction.test.ts +++ b/packages/database-compat/test/transaction.test.ts @@ -15,17 +15,18 @@ * limitations under the License. */ -import firebase from '@firebase/app'; +import firebase from '@firebase/app-compat'; +import { _TEST_ACCESS_hijackHash as hijackHash } from '@firebase/database'; import { Deferred } from '@firebase/util'; import { expect } from 'chai'; -import { Reference } from '../src/api/Reference'; -import { hijackHash } from '../src/api/test_access'; - import { EventAccumulator, EventAccumulatorFactory -} from './helpers/EventAccumulator'; +} from '../../database/test/helpers/EventAccumulator'; +import { Reference } from '../src/api/Reference'; + + import { eventTestHelper } from './helpers/events'; import { canCreateExtraConnections, @@ -34,7 +35,7 @@ import { getVal } from './helpers/util'; -import '../index'; +import '../src/index'; describe('Transaction Tests', () => { // Tests that use hijackHash() should set restoreHash to the restore function diff --git a/packages-exp/app-exp/tsconfig.json b/packages/database-compat/tsconfig.json similarity index 88% rename from packages-exp/app-exp/tsconfig.json rename to packages/database-compat/tsconfig.json index 735ea623511..ce12ac3c5dc 100644 --- a/packages-exp/app-exp/tsconfig.json +++ b/packages/database-compat/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../config/tsconfig.base.json", "compilerOptions": { "outDir": "dist", + "strict": false, "downlevelIteration": true }, "exclude": [ diff --git a/packages/database-types/index.d.ts b/packages/database-types/index.d.ts index 00b0154a4bf..9d4cce3742d 100644 --- a/packages/database-types/index.d.ts +++ b/packages/database-types/index.d.ts @@ -161,6 +161,6 @@ export function enableLogging( declare module '@firebase/component' { interface NameServiceMapping { - 'database': FirebaseDatabase; + 'database-compat': FirebaseDatabase; } } diff --git a/packages/database/.npmignore b/packages/database/.npmignore deleted file mode 100644 index c0e147cc9fc..00000000000 --- a/packages/database/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -# Directories not needed by end users -/src -test - -# Files not needed by end users -gulpfile.js -index.ts -index.node.ts -karma.conf.js -tsconfig.json \ No newline at end of file diff --git a/packages/database/api-extractor.json b/packages/database/api-extractor.json index af5554eb1e4..deee6510e4b 100644 --- a/packages/database/api-extractor.json +++ b/packages/database/api-extractor.json @@ -1,5 +1,11 @@ { "extends": "../../config/api-extractor.json", // Point it to your entry point d.ts file. - "mainEntryPointFilePath": "/dist/exp/index.d.ts" + "mainEntryPointFilePath": "/dist/public.d.ts", + "apiReport": { + /** + * apiReport is handled by repo-scripts/prune-dts/extract-public-api.ts + */ + "enabled": false + } } \ No newline at end of file diff --git a/packages/database/compat/package.json b/packages/database/compat/package.json deleted file mode 100644 index 7a6f2c755be..00000000000 --- a/packages/database/compat/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@firebase/database-compat", - "version": "0.0.900", - "description": "The Realtime Database component of the Firebase JS SDK.", - "author": "Firebase (https://firebase.google.com/)", - "main": "../dist/compat/cjs/index.js", - "browser": "../dist/compat/esm2017/index.js", - "module": "../dist/compat/esm2017/index.js", - "esm5": "../dist/compat/esm5/index.js", - "license": "Apache-2.0", - "typings": "../dist/compat/esm2017/compat/index.d.ts" - } - \ No newline at end of file diff --git a/packages/database/exp/package.json b/packages/database/exp/package.json deleted file mode 100644 index e11ab55d34d..00000000000 --- a/packages/database/exp/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@firebase/database-exp", - "description": "A version of the Realtime Database SDK that is compatible with the tree-shakeable Firebase SDK", - "main": "../dist/exp/index.node.cjs.js", - "browser": "../dist/exp/index.esm2017.js", - "module": "../dist/exp/index.esm2017.js", - "esm5": "../dist/exp/index.esm5.js", - "typings": "../dist/exp/index.d.ts" -} diff --git a/packages/database/index.node.ts b/packages/database/index.node.ts deleted file mode 100644 index fd0fd6b1358..00000000000 --- a/packages/database/index.node.ts +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp, FirebaseNamespace } from '@firebase/app-types'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { FirebaseAuthInternal } from '@firebase/auth-interop-types'; -import { Component, ComponentType } from '@firebase/component'; -import * as types from '@firebase/database-types'; -import { CONSTANTS, isNodeSdk } from '@firebase/util'; -import { Client } from 'faye-websocket'; - -import { name, version } from './package.json'; -import { Database } from './src/api/Database'; -import * as INTERNAL from './src/api/internal'; -import { DataSnapshot, Query, Reference } from './src/api/Reference'; -import * as TEST_ACCESS from './src/api/test_access'; -import { setSDKVersion } from './src/core/version'; -import { enableLogging, repoManagerDatabaseFromApp } from './src/exp/Database'; -import { setWebSocketImpl } from './src/realtime/WebSocketConnection'; - -setWebSocketImpl(Client); - -const ServerValue = Database.ServerValue; - -/** - * A one off register function which returns a database based on the app and - * passed database URL. (Used by the Admin SDK) - * - * @param app - A valid FirebaseApp-like object - * @param url - A valid Firebase databaseURL - * @param version - custom version e.g. firebase-admin version - * @param nodeAdmin - true if the SDK is being initialized from Firebase Admin. - */ -export function initStandalone( - app: FirebaseApp, - url: string, - version: string, - nodeAdmin = true -) { - CONSTANTS.NODE_ADMIN = nodeAdmin; - return INTERNAL.initStandalone({ - app, - url, - version, - // firebase-admin-node's app.INTERNAL implements FirebaseAuthInternal interface - // eslint-disable-next-line @typescript-eslint/no-explicit-any - customAuthImpl: (app as any).INTERNAL as FirebaseAuthInternal, - namespace: { - Reference, - Query, - Database, - DataSnapshot, - enableLogging, - INTERNAL, - ServerValue, - TEST_ACCESS - }, - nodeAdmin - }); -} - -export function registerDatabase(instance: FirebaseNamespace) { - // set SDK_VERSION - setSDKVersion(instance.SDK_VERSION); - - // Register the Database Service with the 'firebase' namespace. - const namespace = (instance as _FirebaseNamespace).INTERNAL.registerComponent( - new Component( - 'database', - (container, { instanceIdentifier: url }) => { - /* Dependencies */ - // getImmediate for FirebaseApp will always succeed - const app = container.getProvider('app').getImmediate(); - const authProvider = container.getProvider('auth-internal'); - const appCheckProvider = container.getProvider('app-check-internal'); - - return new Database( - repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url), - app - ); - }, - ComponentType.PUBLIC - ) - .setServiceProps( - // firebase.database namespace properties - { - Reference, - Query, - Database, - DataSnapshot, - enableLogging, - INTERNAL, - ServerValue, - TEST_ACCESS - } - ) - .setMultipleInstances(true) - ); - - instance.registerVersion(name, version, 'node'); - - if (isNodeSdk()) { - module.exports = Object.assign({}, namespace, { initStandalone }); - } -} - -try { - // If @firebase/app is not present, skip registering database. - // It could happen when this package is used in firebase-admin which doesn't depend on @firebase/app. - // Previously firebase-admin depends on @firebase/app, which causes version conflict on - // @firebase/app when used together with the js sdk. More detail: - // https://github.com/firebase/firebase-js-sdk/issues/1696#issuecomment-501546596 - // eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-require-imports - const firebase = require('@firebase/app').default; // Only present for v8, undefined for v9 (should skip). - if (firebase) { - registerDatabase(firebase); - } -} catch (err) { - // catch and ignore 'MODULE_NOT_FOUND' error in firebase-admin context - // we can safely ignore this error because RTDB in firebase-admin works without @firebase/app - if (err.code !== 'MODULE_NOT_FOUND') { - throw err; - } -} - -// Types to export for the admin SDK -export { Database, Query, Reference, enableLogging, ServerValue }; - -export { OnDisconnect } from './src/api/onDisconnect'; - -declare module '@firebase/app-types' { - interface FirebaseNamespace { - database?: { - (app?: FirebaseApp): types.FirebaseDatabase; - enableLogging: typeof types.enableLogging; - ServerValue: types.ServerValue; - Database: typeof types.FirebaseDatabase; - }; - } - interface FirebaseApp { - database?(): types.FirebaseDatabase; - } -} -export { DataSnapshot } from './src/api/Reference'; diff --git a/packages/database/index.ts b/packages/database/index.ts deleted file mode 100644 index 23e2bf6ded8..00000000000 --- a/packages/database/index.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line import/no-extraneous-dependencies -import firebase from '@firebase/app'; -import { FirebaseNamespace } from '@firebase/app-types'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { Component, ComponentType } from '@firebase/component'; -import * as types from '@firebase/database-types'; -import { isNodeSdk } from '@firebase/util'; - -import { name, version } from './package.json'; -import { Database } from './src/api/Database'; -import * as INTERNAL from './src/api/internal'; -import { DataSnapshot, Query, Reference } from './src/api/Reference'; -import * as TEST_ACCESS from './src/api/test_access'; -import { enableLogging } from './src/core/util/util'; -import { setSDKVersion } from './src/core/version'; -import { repoManagerDatabaseFromApp } from './src/exp/Database'; - -const ServerValue = Database.ServerValue; - -export function registerDatabase(instance: FirebaseNamespace) { - // set SDK_VERSION - setSDKVersion(instance.SDK_VERSION); - - // Register the Database Service with the 'firebase' namespace. - const namespace = (instance as _FirebaseNamespace).INTERNAL.registerComponent( - new Component( - 'database', - (container, { instanceIdentifier: url }) => { - /* Dependencies */ - // getImmediate for FirebaseApp will always succeed - const app = container.getProvider('app').getImmediate(); - const authProvider = container.getProvider('auth-internal'); - const appCheckProvider = container.getProvider('app-check-internal'); - - return new Database( - repoManagerDatabaseFromApp(app, authProvider, appCheckProvider, url), - app - ); - }, - ComponentType.PUBLIC - ) - .setServiceProps( - // firebase.database namespace properties - { - Reference, - Query, - Database, - DataSnapshot, - enableLogging, - INTERNAL, - ServerValue, - TEST_ACCESS - } - ) - .setMultipleInstances(true) - ); - - instance.registerVersion(name, version); - - if (isNodeSdk()) { - module.exports = namespace; - } -} - -registerDatabase(firebase); - -// Types to export for the admin SDK -export { Database, Query, Reference, enableLogging, ServerValue }; - -export { DataSnapshot } from './src/api/Reference'; -export { OnDisconnect } from './src/api/onDisconnect'; - -declare module '@firebase/app-types' { - interface FirebaseNamespace { - database?: { - (app?: FirebaseApp): types.FirebaseDatabase; - enableLogging: typeof types.enableLogging; - ServerValue: types.ServerValue; - Database: typeof types.FirebaseDatabase; - }; - } - interface FirebaseApp { - database?(databaseURL?: string): types.FirebaseDatabase; - } -} diff --git a/packages/database/karma.conf.js b/packages/database/karma.conf.js index 566897507b1..d51e08d046e 100644 --- a/packages/database/karma.conf.js +++ b/packages/database/karma.conf.js @@ -15,8 +15,6 @@ * limitations under the License. */ -const karma = require('karma'); -const path = require('path'); const karmaBase = require('../../config/karma.base'); const files = [`test/**/*.test.ts`]; diff --git a/packages/database/package.json b/packages/database/package.json index 341ea36c98f..edf1a1b471f 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -4,9 +4,9 @@ "description": "", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.node.cjs.js", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "esm5": "dist/index.esm5.js", "files": [ "dist" ], @@ -14,29 +14,23 @@ "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "prettier": "prettier --write '*.js' '*.ts' '@(exp|src|test)/**/*.ts'", - "build": "run-p build:classic build:exp && yarn build:compat", - "build:classic": "rollup -c rollup.config.js", - "build:exp": "rollup -c rollup.config.exp.js && yarn api-report", - "build:compat": "rollup -c rollup.config.compat.js && yarn add-compat-overloads", - "build:exp:release": "yarn build:exp && yarn build:compat", + "build": "rollup -c rollup.config.js && yarn api-report", "build:deps": "lerna run --scope @firebase/'{app,database}' --include-dependencies build", "dev": "rollup -c -w", "test": "run-p lint test:emulator", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:emulator", "test:all": "run-p lint test:browser test:node", "test:browser": "karma start --single-run", - "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file index.node.ts --config ../../config/mocharc.node.js", + "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", "test:emulator": "ts-node --compiler-options='{\"module\":\"commonjs\"}' ../../scripts/emulator-testing/database-test-runner.ts", - "api-report": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package database --packageRoot . --typescriptDts ./dist/exp/exp/index.d.ts --rollupDts ./dist/exp/private.d.ts --untrimmedRollupDts ./dist/exp/internal.d.ts --publicDts ./dist/exp/index.d.ts && yarn api-report:api-json", + "api-report": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package database --packageRoot . --typescriptDts ./dist/src/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/public.d.ts && yarn api-report:api-json", "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", "doc": "api-documenter markdown --input temp --output docs", - "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i dist/exp/index.d.ts -o dist/compat/esm2017/compat/index.d.ts -a -r FirebaseDatabase:types.FirebaseDatabase -r Query:types.Query -r Reference:types.Reference -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/database" + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/public.d.ts" }, "license": "Apache-2.0", "peerDependencies": {}, "dependencies": { - "@firebase/database-types": "0.8.0", "@firebase/logger": "0.2.6", "@firebase/util": "1.3.0", "@firebase/component": "0.5.6", @@ -46,7 +40,6 @@ }, "devDependencies": { "@firebase/app": "0.6.30", - "@firebase/app-types": "0.6.3", "rollup": "2.52.2", "rollup-plugin-typescript2": "0.30.0", "typescript": "4.2.2" @@ -59,7 +52,7 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/index.d.ts", + "typings": "dist/src/index.d.ts", "nyc": { "extension": [ ".ts" diff --git a/packages/database/rollup.config.compat.js b/packages/database/rollup.config.compat.js deleted file mode 100644 index 4892413e42d..00000000000 --- a/packages/database/rollup.config.compat.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import path from 'path'; -import { getImportPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -import compatPkg from './compat/package.json'; -import pkg from './package.json'; - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app', - '@firebase/database' -]; - -function onWarn(warning, defaultWarn) { - if (warning.code === 'CIRCULAR_DEPENDENCY') { - throw new Error(warning); - } - defaultWarn(warning); -} - -/** - * ES5 Builds - */ -const es5BuildPlugins = [ - typescriptPlugin({ - typescript, - abortOnError: false, - transformers: [ - getImportPathTransformer({ - // ../../exp/index - pattern: /^.*exp\/index$/, - template: ['@firebase/database'] - }) - ] - }), - json() -]; - -const es5Builds = [ - /** - * Node.js Build - */ - { - input: 'compat/index.node.ts', - output: [ - { - file: path.resolve('compat', compatPkg.main), - format: 'cjs', - sourcemap: true - } - ], - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: false - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - onwarn: onWarn - }, - /** - * Browser Builds - */ - { - input: 'compat/index.ts', - output: [ - { - file: path.resolve('compat', compatPkg.esm5), - format: 'es', - sourcemap: true - } - ], - plugins: es5BuildPlugins, - treeshake: { - moduleSideEffects: false - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - onwarn: onWarn - } -]; - -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - transformers: [ - getImportPathTransformer({ - // ../../exp/index - pattern: /^.*exp\/index$/, - template: ['@firebase/database'] - }) - ] - }), - json({ preferConst: true }) -]; - -const es2017Builds = [ - /** - * Browser Build - */ - { - input: 'compat/index.ts', - output: [ - { - file: path.resolve('compat', compatPkg.browser), - format: 'es', - sourcemap: true - } - ], - plugins: es2017BuildPlugins, - treeshake: { - moduleSideEffects: false - }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - onwarn: onWarn - } -]; - -export default [...es5Builds, ...es2017Builds]; diff --git a/packages/database/rollup.config.js b/packages/database/rollup.config.js index f44d9c59251..d334f2a0486 100644 --- a/packages/database/rollup.config.js +++ b/packages/database/rollup.config.js @@ -20,9 +20,10 @@ import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; import pkg from './package.json'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); +const deps = [ + ...Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }), + '@firebase/app' +]; function onWarn(warning, defaultWarn) { if (warning.code === 'CIRCULAR_DEPENDENCY') { @@ -36,7 +37,8 @@ function onWarn(warning, defaultWarn) { */ const es5BuildPlugins = [ typescriptPlugin({ - typescript + typescript, + abortOnError: false }), json() ]; @@ -46,27 +48,33 @@ const es5Builds = [ * Node.js Build */ { - input: 'index.node.ts', + input: 'src/index.node.ts', output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - onwarn: onWarn, treeshake: { moduleSideEffects: false - } + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + onwarn: onWarn }, /** * Browser Builds */ { - input: 'index.ts', - output: [{ file: pkg.module, format: 'es', sourcemap: true }], + input: 'src/index.ts', + output: [ + { + file: pkg.esm5, + format: 'es', + sourcemap: true + } + ], plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - onwarn: onWarn, treeshake: { moduleSideEffects: false - } + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + onwarn: onWarn } ]; @@ -80,7 +88,8 @@ const es2017BuildPlugins = [ compilerOptions: { target: 'es2017' } - } + }, + abortOnError: false }), json({ preferConst: true }) ]; @@ -90,14 +99,20 @@ const es2017Builds = [ * Browser Build */ { - input: 'index.ts', - output: [{ file: pkg.esm2017, format: 'es', sourcemap: true }], + input: 'src/index.ts', + output: [ + { + file: pkg.browser, + format: 'es', + sourcemap: true + } + ], plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - onwarn: onWarn, treeshake: { moduleSideEffects: false - } + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + onwarn: onWarn } ]; diff --git a/packages/database/exp/api.ts b/packages/database/src/api.ts similarity index 58% rename from packages/database/exp/api.ts rename to packages/database/src/api.ts index 974e6976f51..9228a2be63e 100644 --- a/packages/database/exp/api.ts +++ b/packages/database/src/api.ts @@ -21,17 +21,16 @@ export { getDatabase, goOffline, goOnline, - connectDatabaseEmulator, - repoManagerDatabaseFromApp as _repoManagerDatabaseFromApp -} from '../src/exp/Database'; + connectDatabaseEmulator +} from './api/Database'; export { Query, DatabaseReference, ListenOptions, Unsubscribe, ThenableReference -} from '../src/exp/Reference'; -export { OnDisconnect } from '../src/exp/OnDisconnect'; +} from './api/Reference'; +export { OnDisconnect } from './api/OnDisconnect'; export { DataSnapshot, EventType, @@ -65,13 +64,32 @@ export { startAfter, startAt, update, - child, - ReferenceImpl as _ReferenceImpl, - QueryImpl as _QueryImpl -} from '../src/exp/Reference_impl'; -export { increment, serverTimestamp } from '../src/exp/ServerValue'; + child +} from './api/Reference_impl'; +export { increment, serverTimestamp } from './api/ServerValue'; export { runTransaction, TransactionOptions, TransactionResult -} from '../src/exp/Transaction'; +} from './api/Transaction'; + +// internal exports +export { setSDKVersion as _setSDKVersion } from './core/version'; +export { + ReferenceImpl as _ReferenceImpl, + QueryImpl as _QueryImpl +} from './api/Reference_impl'; +export { repoManagerDatabaseFromApp as _repoManagerDatabaseFromApp } from './api/Database'; +export { + validatePathString as _validatePathString, + validateWritablePath as _validateWritablePath +} from './core/util/validation'; +export { UserCallback as _UserCallback } from './core/view/EventRegistration'; +export { QueryParams as _QueryParams } from './core/view/QueryParams'; + +/* eslint-disable camelcase */ +export { + hijackHash as _TEST_ACCESS_hijackHash, + forceRestClient as _TEST_ACCESS_forceRestClient +} from './api/test_access'; +/* eslint-enable camelcase */ diff --git a/packages/database/src/api/Database.ts b/packages/database/src/api/Database.ts index 671104f848e..40cc422b3c1 100644 --- a/packages/database/src/api/Database.ts +++ b/packages/database/src/api/Database.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,117 +14,395 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// eslint-disable-next-line import/no-extraneous-dependencies - -import { FirebaseApp } from '@firebase/app-types'; -import { FirebaseService } from '@firebase/app-types/private'; -import { - validateArgCount, - Compat, - EmulatorMockTokenOptions -} from '@firebase/util'; - -import { - goOnline, - connectDatabaseEmulator, - goOffline, - ref, - refFromURL, - increment, - serverTimestamp -} from '../../exp/index'; // import from the exp public API - -import { Reference } from './Reference'; - -// TODO: revert to import {FirebaseDatabase as ExpDatabase} from '@firebase/database' once modular SDK goes GA -/** - * This is a workaround for an issue in the no-modular '@firebase/database' where its typings - * reference types from `@firebase/app-exp`. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type ExpDatabase = any; - -/** - * Class representing a firebase database. - */ -export class Database implements FirebaseService, Compat { - static readonly ServerValue = { - TIMESTAMP: serverTimestamp(), - increment: (delta: number) => increment(delta) - }; - - /** - * The constructor should not be called by users of our public API. - */ - constructor(readonly _delegate: ExpDatabase, readonly app: FirebaseApp) {} - - INTERNAL = { - delete: () => this._delegate._delete() - }; - - /** - * Modify this instance to communicate with the Realtime Database emulator. - * - *

Note: This method must be called before performing any other operation. - * - * @param host - the emulator host (ex: localhost) - * @param port - the emulator port (ex: 8080) - * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules - */ - useEmulator( - host: string, - port: number, - options: { - mockUserToken?: EmulatorMockTokenOptions; - } = {} - ): void { - connectDatabaseEmulator(this._delegate, host, port, options); - } - - /** - * Returns a reference to the root or to the path specified in the provided - * argument. - * - * @param path - The relative string path or an existing Reference to a database - * location. - * @throws If a Reference is provided, throws if it does not belong to the - * same project. - * @returns Firebase reference. - */ - ref(path?: string): Reference; - ref(path?: Reference): Reference; - ref(path?: string | Reference): Reference { - validateArgCount('database.ref', 0, 1, arguments.length); - if (path instanceof Reference) { - const childRef = refFromURL(this._delegate, path.toString()); - return new Reference(this, childRef); - } else { - const childRef = ref(this._delegate, path); - return new Reference(this, childRef); - } - } - - /** - * Returns a reference to the root or the path specified in url. - * We throw a exception if the url is not in the same domain as the - * current repo. - * @returns Firebase reference. - */ - refFromURL(url: string): Reference { - const apiName = 'database.refFromURL'; - validateArgCount(apiName, 1, 1, arguments.length); - const childRef = refFromURL(this._delegate, url); - return new Reference(this, childRef); - } - - // Make individual repo go offline. - goOffline(): void { - validateArgCount('database.goOffline', 0, 0, arguments.length); - return goOffline(this._delegate); - } - - goOnline(): void { - validateArgCount('database.goOnline', 0, 0, arguments.length); - return goOnline(this._delegate); - } -} + // eslint-disable-next-line import/no-extraneous-dependencies + import { + _FirebaseService, + _getProvider, + FirebaseApp, + getApp + } from '@firebase/app'; + import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; + import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; + import { Provider } from '@firebase/component'; + import { + getModularInstance, + createMockUserToken, + EmulatorMockTokenOptions + } from '@firebase/util'; + + import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider'; + import { + AuthTokenProvider, + EmulatorTokenProvider, + FirebaseAuthTokenProvider + } from '../core/AuthTokenProvider'; + import { Repo, repoInterrupt, repoResume, repoStart } from '../core/Repo'; + import { RepoInfo } from '../core/RepoInfo'; + import { parseRepoInfo } from '../core/util/libs/parser'; + import { newEmptyPath, pathIsEmpty } from '../core/util/Path'; + import { + fatal, + log, + enableLogging as enableLoggingImpl + } from '../core/util/util'; + import { validateUrl } from '../core/util/validation'; + + import { ReferenceImpl } from './Reference_impl'; + + /** + * This variable is also defined in the firebase Node.js Admin SDK. Before + * modifying this definition, consult the definition in: + * + * https://github.com/firebase/firebase-admin-node + * + * and make sure the two are consistent. + */ + const FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST'; + + /** + * Creates and caches `Repo` instances. + */ + const repos: { + [appName: string]: { + [dbUrl: string]: Repo; + }; + } = {}; + + /** + * If true, any new `Repo` will be created to use `ReadonlyRestClient` (for testing purposes). + */ + let useRestClient = false; + + /** + * Update an existing `Repo` in place to point to a new host/port. + */ + function repoManagerApplyEmulatorSettings( + repo: Repo, + host: string, + port: number, + tokenProvider?: AuthTokenProvider + ): void { + repo.repoInfo_ = new RepoInfo( + `${host}:${port}`, + /* secure= */ false, + repo.repoInfo_.namespace, + repo.repoInfo_.webSocketOnly, + repo.repoInfo_.nodeAdmin, + repo.repoInfo_.persistenceKey, + repo.repoInfo_.includeNamespaceInQueryParams + ); + + if (tokenProvider) { + repo.authTokenProvider_ = tokenProvider; + } + } + + /** + * This function should only ever be called to CREATE a new database instance. + * @internal + */ + export function repoManagerDatabaseFromApp( + app: FirebaseApp, + authProvider: Provider, + appCheckProvider?: Provider, + url?: string, + nodeAdmin?: boolean + ): Database { + let dbUrl: string | undefined = url || app.options.databaseURL; + if (dbUrl === undefined) { + if (!app.options.projectId) { + fatal( + "Can't determine Firebase Database URL. Be sure to include " + + ' a Project ID when calling firebase.initializeApp().' + ); + } + + log('Using default host for project ', app.options.projectId); + dbUrl = `${app.options.projectId}-default-rtdb.firebaseio.com`; + } + + let parsedUrl = parseRepoInfo(dbUrl, nodeAdmin); + let repoInfo = parsedUrl.repoInfo; + + let isEmulator: boolean; + + let dbEmulatorHost: string | undefined = undefined; + if (typeof process !== 'undefined') { + dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR]; + } + + if (dbEmulatorHost) { + isEmulator = true; + dbUrl = `http://${dbEmulatorHost}?ns=${repoInfo.namespace}`; + parsedUrl = parseRepoInfo(dbUrl, nodeAdmin); + repoInfo = parsedUrl.repoInfo; + } else { + isEmulator = !parsedUrl.repoInfo.secure; + } + + const authTokenProvider = + nodeAdmin && isEmulator + ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER) + : new FirebaseAuthTokenProvider(app.name, app.options, authProvider); + + validateUrl('Invalid Firebase Database URL', parsedUrl); + if (!pathIsEmpty(parsedUrl.path)) { + fatal( + 'Database URL must point to the root of a Firebase Database ' + + '(not including a child path).' + ); + } + + const repo = repoManagerCreateRepo( + repoInfo, + app, + authTokenProvider, + new AppCheckTokenProvider(app.name, appCheckProvider) + ); + return new Database(repo, app); + } + + /** + * Remove the repo and make sure it is disconnected. + * + */ + function repoManagerDeleteRepo(repo: Repo, appName: string): void { + const appRepos = repos[appName]; + // This should never happen... + if (!appRepos || appRepos[repo.key] !== repo) { + fatal(`Database ${appName}(${repo.repoInfo_}) has already been deleted.`); + } + repoInterrupt(repo); + delete appRepos[repo.key]; + } + + /** + * Ensures a repo doesn't already exist and then creates one using the + * provided app. + * + * @param repoInfo - The metadata about the Repo + * @returns The Repo object for the specified server / repoName. + */ + function repoManagerCreateRepo( + repoInfo: RepoInfo, + app: FirebaseApp, + authTokenProvider: AuthTokenProvider, + appCheckProvider: AppCheckTokenProvider + ): Repo { + let appRepos = repos[app.name]; + + if (!appRepos) { + appRepos = {}; + repos[app.name] = appRepos; + } + + let repo = appRepos[repoInfo.toURLString()]; + if (repo) { + fatal( + 'Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.' + ); + } + repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider); + appRepos[repoInfo.toURLString()] = repo; + + return repo; + } + + /** + * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos. + */ + export function repoManagerForceRestClient(forceRestClient: boolean): void { + useRestClient = forceRestClient; + } + + /** + * Class representing a Firebase Realtime Database. + */ + export class Database implements _FirebaseService { + /** Represents a `Database` instance. */ + readonly 'type' = 'database'; + + /** Track if the instance has been used (root or repo accessed) */ + _instanceStarted: boolean = false; + + /** Backing state for root_ */ + private _rootInternal?: ReferenceImpl; + + /** @hideconstructor */ + constructor( + public _repoInternal: Repo, + /** The {@link @firebase/app#FirebaseApp} associated with this Realtime Database instance. */ + readonly app: FirebaseApp + ) {} + + get _repo(): Repo { + if (!this._instanceStarted) { + repoStart( + this._repoInternal, + this.app.options.appId, + this.app.options['databaseAuthVariableOverride'] + ); + this._instanceStarted = true; + } + return this._repoInternal; + } + + get _root(): ReferenceImpl { + if (!this._rootInternal) { + this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath()); + } + return this._rootInternal; + } + + _delete(): Promise { + if (this._rootInternal !== null) { + repoManagerDeleteRepo(this._repo, this.app.name); + this._repoInternal = null; + this._rootInternal = null; + } + return Promise.resolve(); + } + + _checkNotDeleted(apiName: string) { + if (this._rootInternal === null) { + fatal('Cannot call ' + apiName + ' on a deleted database.'); + } + } + } + + /** + * Returns the instance of the Realtime Database SDK that is associated + * with the provided {@link @firebase/app#FirebaseApp}. Initializes a new instance with + * with default settings if no instance exists or if the existing instance uses + * a custom database URL. + * + * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned Realtime + * Database instance is associated with. + * @param url - The URL of the Realtime Database instance to connect to. If not + * provided, the SDK connects to the default instance of the Firebase App. + * @returns The `Database` instance of the provided app. + */ + export function getDatabase( + app: FirebaseApp = getApp(), + url?: string + ): Database { + return _getProvider(app, 'database').getImmediate({ + identifier: url + }) as Database; + } + + /** + * Modify the provided instance to communicate with the Realtime Database + * emulator. + * + *

Note: This method must be called before performing any other operation. + * + * @param db - The instance to modify. + * @param host - The emulator host (ex: localhost) + * @param port - The emulator port (ex: 8080) + * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules + */ + export function connectDatabaseEmulator( + db: Database, + host: string, + port: number, + options: { + mockUserToken?: EmulatorMockTokenOptions | string; + } = {} + ): void { + db = getModularInstance(db); + db._checkNotDeleted('useEmulator'); + if (db._instanceStarted) { + fatal( + 'Cannot call useEmulator() after instance has already been initialized.' + ); + } + + const repo = db._repoInternal; + let tokenProvider: EmulatorTokenProvider | undefined = undefined; + if (repo.repoInfo_.nodeAdmin) { + if (options.mockUserToken) { + fatal( + 'mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".' + ); + } + tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER); + } else if (options.mockUserToken) { + const token = + typeof options.mockUserToken === 'string' + ? options.mockUserToken + : createMockUserToken(options.mockUserToken, db.app.options.projectId); + tokenProvider = new EmulatorTokenProvider(token); + } + + // Modify the repo to apply emulator settings + repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider); + } + + /** + * Disconnects from the server (all Database operations will be completed + * offline). + * + * The client automatically maintains a persistent connection to the Database + * server, which will remain active indefinitely and reconnect when + * disconnected. However, the `goOffline()` and `goOnline()` methods may be used + * to control the client connection in cases where a persistent connection is + * undesirable. + * + * While offline, the client will no longer receive data updates from the + * Database. However, all Database operations performed locally will continue to + * immediately fire events, allowing your application to continue behaving + * normally. Additionally, each operation performed locally will automatically + * be queued and retried upon reconnection to the Database server. + * + * To reconnect to the Database and begin receiving remote events, see + * `goOnline()`. + * + * @param db - The instance to disconnect. + */ + export function goOffline(db: Database): void { + db = getModularInstance(db); + db._checkNotDeleted('goOffline'); + repoInterrupt(db._repo); + } + + /** + * Reconnects to the server and synchronizes the offline Database state + * with the server state. + * + * This method should be used after disabling the active connection with + * `goOffline()`. Once reconnected, the client will transmit the proper data + * and fire the appropriate events so that your client "catches up" + * automatically. + * + * @param db - The instance to reconnect. + */ + export function goOnline(db: Database): void { + db = getModularInstance(db); + db._checkNotDeleted('goOnline'); + repoResume(db._repo); + } + + /** + * Logs debugging information to the console. + * + * @param enabled - Enables logging if `true`, disables logging if `false`. + * @param persistent - Remembers the logging state between page refreshes if + * `true`. + */ + export function enableLogging(enabled: boolean, persistent?: boolean); + + /** + * Logs debugging information to the console. + * + * @param logger - A custom logger function to control how things get logged. + */ + export function enableLogging(logger: (message: string) => unknown); + + export function enableLogging( + logger: boolean | ((message: string) => unknown), + persistent?: boolean + ): void { + enableLoggingImpl(logger, persistent); + } + \ No newline at end of file diff --git a/packages/database/src/exp/OnDisconnect.ts b/packages/database/src/api/OnDisconnect.ts similarity index 100% rename from packages/database/src/exp/OnDisconnect.ts rename to packages/database/src/api/OnDisconnect.ts diff --git a/packages/database/src/api/Reference.ts b/packages/database/src/api/Reference.ts index 4bc9b63365b..c2e97aa229e 100644 --- a/packages/database/src/api/Reference.ts +++ b/packages/database/src/api/Reference.ts @@ -1,6 +1,10 @@ +import { Repo } from '../core/Repo'; +import { Path } from '../core/util/Path'; +import { QueryContext } from '../core/view/EventRegistration'; + /** * @license - * Copyright 2017 Google LLC + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,782 +19,117 @@ * limitations under the License. */ -import { - Compat, - Deferred, - errorPrefix, - validateArgCount, - validateCallback, - validateContextObject -} from '@firebase/util'; - -import { - OnDisconnect as ExpOnDisconnect, - off, - onChildAdded, - onChildChanged, - onChildMoved, - onChildRemoved, - onValue, - EventType, - limitToFirst, - query, - limitToLast, - orderByChild, - orderByKey, - orderByValue, - orderByPriority, - startAt, - startAfter, - endAt, - endBefore, - equalTo, - get, - set, - update, - setWithPriority, - remove, - setPriority, - push, - runTransaction, - _QueryImpl, - _ReferenceImpl, - child -} from '../../exp/index'; // import from the exp public API -import { warn } from '../core/util/util'; -import { - validateBoolean, - validateEventType, - validatePathString, - validateWritablePath -} from '../core/util/validation'; -import { UserCallback } from '../core/view/EventRegistration'; -import { QueryParams } from '../core/view/QueryParams'; -import { ThenableReferenceImpl } from '../exp/Reference_impl'; - -import { Database } from './Database'; -import { OnDisconnect } from './onDisconnect'; -import { TransactionResult } from './TransactionResult'; - -// TODO: revert to import { DataSnapshot as ExpDataSnapshot, Query as ExpQuery, -// Reference as ExpReference,} from '../../exp/index'; once the modular SDK goes GA -/** - * This is part of a workaround for an issue in the no-modular '@firebase/database' where its typings - * reference types from `@firebase/app-exp`. - */ - -/* eslint-disable @typescript-eslint/no-explicit-any */ -type ExpDataSnapshot = any; -type ExpQuery = any; -type ExpReference = any; -/* eslint-enable @typescript-eslint/no-explicit-any */ - /** - * Class representing a firebase data snapshot. It wraps a SnapshotNode and - * surfaces the public methods (val, forEach, etc.) we want to expose. + * A `Query` sorts and filters the data at a Database location so only a subset + * of the child data is included. This can be used to order a collection of + * data by some attribute (for example, height of dinosaurs) as well as to + * restrict a large list of items (for example, chat messages) down to a number + * suitable for synchronizing to the client. Queries are created by chaining + * together one or more of the filter methods defined here. + * + * Just as with a `DatabaseReference`, you can receive data from a `Query` by using the + * `on*()` methods. You will only receive events and `DataSnapshot`s for the + * subset of the data that matches your query. + * + * See {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data} + * for more information. */ -export class DataSnapshot implements Compat { - constructor( - readonly _database: Database, - readonly _delegate: ExpDataSnapshot - ) {} +export interface Query extends QueryContext { + /** The `DatabaseReference` for the `Query`'s location. */ + readonly ref: DatabaseReference; /** - * Retrieves the snapshot contents as JSON. Returns null if the snapshot is - * empty. + * Returns whether or not the current and provided queries represent the same + * location, have the same query parameters, and are from the same instance of + * `FirebaseApp`. * - * @returns JSON representation of the DataSnapshot contents, or null if empty. - */ - val(): unknown { - validateArgCount('DataSnapshot.val', 0, 0, arguments.length); - return this._delegate.val(); - } - - /** - * Returns the snapshot contents as JSON, including priorities of node. Suitable for exporting - * the entire node contents. - * @returns JSON representation of the DataSnapshot contents, or null if empty. - */ - exportVal(): unknown { - validateArgCount('DataSnapshot.exportVal', 0, 0, arguments.length); - return this._delegate.exportVal(); - } - - // Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary - // for end-users - toJSON(): unknown { - // Optional spacer argument is unnecessary because we're depending on recursion rather than stringifying the content - validateArgCount('DataSnapshot.toJSON', 0, 1, arguments.length); - return this._delegate.toJSON(); - } - - /** - * Returns whether the snapshot contains a non-null value. + * Two `DatabaseReference` objects are equivalent if they represent the same location + * and are from the same instance of `FirebaseApp`. * - * @returns Whether the snapshot contains a non-null value, or is empty. - */ - exists(): boolean { - validateArgCount('DataSnapshot.exists', 0, 0, arguments.length); - return this._delegate.exists(); - } - - /** - * Returns a DataSnapshot of the specified child node's contents. + * Two `Query` objects are equivalent if they represent the same location, + * have the same query parameters, and are from the same instance of + * `FirebaseApp`. Equivalent queries share the same sort order, limits, and + * starting and ending points. * - * @param path - Path to a child. - * @returns DataSnapshot for child node. + * @param other - The query to compare against. + * @returns Whether or not the current and provided queries are equivalent. */ - child(path: string): DataSnapshot { - validateArgCount('DataSnapshot.child', 0, 1, arguments.length); - // Ensure the childPath is a string (can be a number) - path = String(path); - validatePathString('DataSnapshot.child', 'path', path, false); - return new DataSnapshot(this._database, this._delegate.child(path)); - } + isEqual(other: Query | null): boolean; /** - * Returns whether the snapshot contains a child at the specified path. + * Returns a JSON-serializable representation of this object. * - * @param path - Path to a child. - * @returns Whether the child exists. + * @returns A JSON-serializable representation of this object. */ - hasChild(path: string): boolean { - validateArgCount('DataSnapshot.hasChild', 1, 1, arguments.length); - validatePathString('DataSnapshot.hasChild', 'path', path, false); - return this._delegate.hasChild(path); - } + toJSON(): string; /** - * Returns the priority of the object, or null if no priority was set. + * Gets the absolute URL for this location. * - * @returns The priority. - */ - getPriority(): string | number | null { - validateArgCount('DataSnapshot.getPriority', 0, 0, arguments.length); - return this._delegate.priority; - } - - /** - * Iterates through child nodes and calls the specified action for each one. + * The `toString()` method returns a URL that is ready to be put into a + * browser, curl command, or a `refFromURL()` call. Since all of those expect + * the URL to be url-encoded, `toString()` returns an encoded URL. * - * @param action - Callback function to be called - * for each child. - * @returns True if forEach was canceled by action returning true for - * one of the child nodes. - */ - forEach(action: (snapshot: DataSnapshot) => boolean | void): boolean { - validateArgCount('DataSnapshot.forEach', 1, 1, arguments.length); - validateCallback('DataSnapshot.forEach', 'action', action, false); - return this._delegate.forEach(expDataSnapshot => - action(new DataSnapshot(this._database, expDataSnapshot)) - ); - } - - /** - * Returns whether this DataSnapshot has children. - * @returns True if the DataSnapshot contains 1 or more child nodes. - */ - hasChildren(): boolean { - validateArgCount('DataSnapshot.hasChildren', 0, 0, arguments.length); - return this._delegate.hasChildren(); - } - - get key() { - return this._delegate.key; - } - - /** - * Returns the number of children for this DataSnapshot. - * @returns The number of children that this DataSnapshot contains. - */ - numChildren(): number { - validateArgCount('DataSnapshot.numChildren', 0, 0, arguments.length); - return this._delegate.size; - } - - /** - * @returns The Firebase reference for the location this snapshot's data came - * from. + * Append '.json' to the returned URL when typed into a browser to download + * JSON-formatted data. If the location is secured (that is, not publicly + * readable), you will get a permission-denied error. + * + * @returns The absolute URL for this location. */ - getRef(): Reference { - validateArgCount('DataSnapshot.ref', 0, 0, arguments.length); - return new Reference(this._database, this._delegate.ref); - } - - get ref(): Reference { - return this.getRef(); - } -} - -export interface SnapshotCallback { - (dataSnapshot: DataSnapshot, previousChildName?: string | null): unknown; + toString(): string; } /** - * A Query represents a filter to be applied to a firebase location. This object purely represents the - * query expression (and exposes our public API to build the query). The actual query logic is in ViewBase.js. + * A `DatabaseReference` represents a specific location in your Database and can be used + * for reading or writing data to that Database location. * - * Since every Firebase reference is a query, Firebase inherits from this object. + * You can reference the root or child location in your Database by calling + * `ref()` or `ref("child/path")`. + * + * Writing is done with the `set()` method and reading can be done with the + * `on*()` method. See {@link + * https://firebase.google.com/docs/database/web/read-and-write} */ -export class Query implements Compat { - constructor(readonly database: Database, readonly _delegate: ExpQuery) {} - - on( - eventType: string, - callback: SnapshotCallback, - cancelCallbackOrContext?: ((a: Error) => unknown) | object | null, - context?: object | null - ): SnapshotCallback { - validateArgCount('Query.on', 2, 4, arguments.length); - validateCallback('Query.on', 'callback', callback, false); - - const ret = Query.getCancelAndContextArgs_( - 'Query.on', - cancelCallbackOrContext, - context - ); - const valueCallback: UserCallback = (expSnapshot, previousChildName?) => { - callback.call( - ret.context, - new DataSnapshot(this.database, expSnapshot), - previousChildName - ); - }; - valueCallback.userCallback = callback; - valueCallback.context = ret.context; - const cancelCallback = ret.cancel?.bind(ret.context); - - switch (eventType) { - case 'value': - onValue(this._delegate, valueCallback, cancelCallback); - return callback; - case 'child_added': - onChildAdded(this._delegate, valueCallback, cancelCallback); - return callback; - case 'child_removed': - onChildRemoved(this._delegate, valueCallback, cancelCallback); - return callback; - case 'child_changed': - onChildChanged(this._delegate, valueCallback, cancelCallback); - return callback; - case 'child_moved': - onChildMoved(this._delegate, valueCallback, cancelCallback); - return callback; - default: - throw new Error( - errorPrefix('Query.on', 'eventType') + - 'must be a valid event type = "value", "child_added", "child_removed", ' + - '"child_changed", or "child_moved".' - ); - } - } - - off( - eventType?: string, - callback?: SnapshotCallback, - context?: object | null - ): void { - validateArgCount('Query.off', 0, 3, arguments.length); - validateEventType('Query.off', eventType, true); - validateCallback('Query.off', 'callback', callback, true); - validateContextObject('Query.off', 'context', context, true); - if (callback) { - const valueCallback: UserCallback = () => {}; - valueCallback.userCallback = callback; - valueCallback.context = context; - off(this._delegate, eventType as EventType, valueCallback); - } else { - off(this._delegate, eventType as EventType | undefined); - } - } - - /** - * Get the server-value for this query, or return a cached value if not connected. - */ - get(): Promise { - return get(this._delegate).then(expSnapshot => { - return new DataSnapshot(this.database, expSnapshot); - }); - } - - /** - * Attaches a listener, waits for the first event, and then removes the listener - */ - once( - eventType: string, - callback?: SnapshotCallback, - failureCallbackOrContext?: ((a: Error) => void) | object | null, - context?: object | null - ): Promise { - validateArgCount('Query.once', 1, 4, arguments.length); - validateCallback('Query.once', 'callback', callback, true); - - const ret = Query.getCancelAndContextArgs_( - 'Query.once', - failureCallbackOrContext, - context - ); - const deferred = new Deferred(); - const valueCallback: UserCallback = (expSnapshot, previousChildName?) => { - const result = new DataSnapshot(this.database, expSnapshot); - if (callback) { - callback.call(ret.context, result, previousChildName); - } - deferred.resolve(result); - }; - valueCallback.userCallback = callback; - valueCallback.context = ret.context; - const cancelCallback = (error: Error) => { - if (ret.cancel) { - ret.cancel.call(ret.context, error); - } - deferred.reject(error); - }; - - switch (eventType) { - case 'value': - onValue(this._delegate, valueCallback, cancelCallback, { - onlyOnce: true - }); - break; - case 'child_added': - onChildAdded(this._delegate, valueCallback, cancelCallback, { - onlyOnce: true - }); - break; - case 'child_removed': - onChildRemoved(this._delegate, valueCallback, cancelCallback, { - onlyOnce: true - }); - break; - case 'child_changed': - onChildChanged(this._delegate, valueCallback, cancelCallback, { - onlyOnce: true - }); - break; - case 'child_moved': - onChildMoved(this._delegate, valueCallback, cancelCallback, { - onlyOnce: true - }); - break; - default: - throw new Error( - errorPrefix('Query.once', 'eventType') + - 'must be a valid event type = "value", "child_added", "child_removed", ' + - '"child_changed", or "child_moved".' - ); - } - - return deferred.promise; - } - - /** - * Set a limit and anchor it to the start of the window. - */ - limitToFirst(limit: number): Query { - validateArgCount('Query.limitToFirst', 1, 1, arguments.length); - return new Query(this.database, query(this._delegate, limitToFirst(limit))); - } - - /** - * Set a limit and anchor it to the end of the window. - */ - limitToLast(limit: number): Query { - validateArgCount('Query.limitToLast', 1, 1, arguments.length); - return new Query(this.database, query(this._delegate, limitToLast(limit))); - } - - /** - * Given a child path, return a new query ordered by the specified grandchild path. - */ - orderByChild(path: string): Query { - validateArgCount('Query.orderByChild', 1, 1, arguments.length); - return new Query(this.database, query(this._delegate, orderByChild(path))); - } - - /** - * Return a new query ordered by the KeyIndex - */ - orderByKey(): Query { - validateArgCount('Query.orderByKey', 0, 0, arguments.length); - return new Query(this.database, query(this._delegate, orderByKey())); - } - - /** - * Return a new query ordered by the PriorityIndex - */ - orderByPriority(): Query { - validateArgCount('Query.orderByPriority', 0, 0, arguments.length); - return new Query(this.database, query(this._delegate, orderByPriority())); - } - - /** - * Return a new query ordered by the ValueIndex - */ - orderByValue(): Query { - validateArgCount('Query.orderByValue', 0, 0, arguments.length); - return new Query(this.database, query(this._delegate, orderByValue())); - } - - startAt( - value: number | string | boolean | null = null, - name?: string | null - ): Query { - validateArgCount('Query.startAt', 0, 2, arguments.length); - return new Query( - this.database, - query(this._delegate, startAt(value, name)) - ); - } - - startAfter( - value: number | string | boolean | null = null, - name?: string | null - ): Query { - validateArgCount('Query.startAfter', 0, 2, arguments.length); - return new Query( - this.database, - query(this._delegate, startAfter(value, name)) - ); - } - - endAt( - value: number | string | boolean | null = null, - name?: string | null - ): Query { - validateArgCount('Query.endAt', 0, 2, arguments.length); - return new Query(this.database, query(this._delegate, endAt(value, name))); - } - - endBefore( - value: number | string | boolean | null = null, - name?: string | null - ): Query { - validateArgCount('Query.endBefore', 0, 2, arguments.length); - return new Query( - this.database, - query(this._delegate, endBefore(value, name)) - ); - } - - /** - * Load the selection of children with exactly the specified value, and, optionally, - * the specified name. - */ - equalTo(value: number | string | boolean | null, name?: string) { - validateArgCount('Query.equalTo', 1, 2, arguments.length); - return new Query( - this.database, - query(this._delegate, equalTo(value, name)) - ); - } - - /** - * @returns URL for this location. - */ - toString(): string { - validateArgCount('Query.toString', 0, 0, arguments.length); - return this._delegate.toString(); - } - - // Do not create public documentation. This is intended to make JSON serialization work but is otherwise unnecessary - // for end-users. - toJSON() { - // An optional spacer argument is unnecessary for a string. - validateArgCount('Query.toJSON', 0, 1, arguments.length); - return this._delegate.toJSON(); - } - - /** - * Return true if this query and the provided query are equivalent; otherwise, return false. - */ - isEqual(other: Query): boolean { - validateArgCount('Query.isEqual', 1, 1, arguments.length); - if (!(other instanceof Query)) { - const error = - 'Query.isEqual failed: First argument must be an instance of firebase.database.Query.'; - throw new Error(error); - } - return this._delegate.isEqual(other._delegate); - } - +export interface DatabaseReference extends Query { /** - * Helper used by .on and .once to extract the context and or cancel arguments. - * @param fnName - The function name (on or once) + * The last part of the `DatabaseReference`'s path. * + * For example, `"ada"` is the key for + * `https://.firebaseio.com/users/ada`. + * + * The key of a root `DatabaseReference` is `null`. */ - private static getCancelAndContextArgs_( - fnName: string, - cancelOrContext?: ((a: Error) => void) | object | null, - context?: object | null - ): { cancel: ((a: Error) => void) | undefined; context: object | undefined } { - const ret: { - cancel: ((a: Error) => void) | null; - context: object | null; - } = { cancel: undefined, context: undefined }; - if (cancelOrContext && context) { - ret.cancel = cancelOrContext as (a: Error) => void; - validateCallback(fnName, 'cancel', ret.cancel, true); - - ret.context = context; - validateContextObject(fnName, 'context', ret.context, true); - } else if (cancelOrContext) { - // we have either a cancel callback or a context. - if (typeof cancelOrContext === 'object' && cancelOrContext !== null) { - // it's a context! - ret.context = cancelOrContext; - } else if (typeof cancelOrContext === 'function') { - ret.cancel = cancelOrContext as (a: Error) => void; - } else { - throw new Error( - errorPrefix(fnName, 'cancelOrContext') + - ' must either be a cancel callback or a context object.' - ); - } - } - return ret; - } - - get ref(): Reference { - return new Reference( - this.database, - new _ReferenceImpl(this._delegate._repo, this._delegate._path) - ); - } -} - -export class Reference extends Query implements Compat { - then: Promise['then']; - catch: Promise['catch']; + readonly key: string | null; /** - * Call options: - * new Reference(Repo, Path) or - * new Reference(url: string, string|RepoManager) + * The parent location of a `DatabaseReference`. * - * Externally - this is the firebase.database.Reference type. + * The parent of a root `DatabaseReference` is `null`. */ - constructor(readonly database: Database, readonly _delegate: ExpReference) { - super( - database, - new _QueryImpl(_delegate._repo, _delegate._path, new QueryParams(), false) - ); - } - - /** @returns {?string} */ - getKey(): string | null { - validateArgCount('Reference.key', 0, 0, arguments.length); - return this._delegate.key; - } - - child(pathString: string): Reference { - validateArgCount('Reference.child', 1, 1, arguments.length); - if (typeof pathString === 'number') { - pathString = String(pathString); - } - return new Reference(this.database, child(this._delegate, pathString)); - } - - /** @returns {?Reference} */ - getParent(): Reference | null { - validateArgCount('Reference.parent', 0, 0, arguments.length); - const parent = this._delegate.parent; - return parent ? new Reference(this.database, parent) : null; - } - - /** @returns {!Reference} */ - getRoot(): Reference { - validateArgCount('Reference.root', 0, 0, arguments.length); - return new Reference(this.database, this._delegate.root); - } - - set( - newVal: unknown, - onComplete?: (error: Error | null) => void - ): Promise { - validateArgCount('Reference.set', 1, 2, arguments.length); - validateCallback('Reference.set', 'onComplete', onComplete, true); - const result = set(this._delegate, newVal); - if (onComplete) { - result.then( - () => onComplete(null), - error => onComplete(error) - ); - } - return result; - } - - update( - values: object, - onComplete?: (a: Error | null) => void - ): Promise { - validateArgCount('Reference.update', 1, 2, arguments.length); - - if (Array.isArray(values)) { - const newObjectToMerge: { [k: string]: unknown } = {}; - for (let i = 0; i < values.length; ++i) { - newObjectToMerge['' + i] = values[i]; - } - values = newObjectToMerge; - warn( - 'Passing an Array to Firebase.update() is deprecated. ' + - 'Use set() if you want to overwrite the existing data, or ' + - 'an Object with integer keys if you really do want to ' + - 'only update some of the children.' - ); - } - validateWritablePath('Reference.update', this._delegate._path); - validateCallback('Reference.update', 'onComplete', onComplete, true); - - const result = update(this._delegate, values); - if (onComplete) { - result.then( - () => onComplete(null), - error => onComplete(error) - ); - } - return result; - } - - setWithPriority( - newVal: unknown, - newPriority: string | number | null, - onComplete?: (a: Error | null) => void - ): Promise { - validateArgCount('Reference.setWithPriority', 2, 3, arguments.length); - validateCallback( - 'Reference.setWithPriority', - 'onComplete', - onComplete, - true - ); + readonly parent: DatabaseReference | null; - const result = setWithPriority(this._delegate, newVal, newPriority); - if (onComplete) { - result.then( - () => onComplete(null), - error => onComplete(error) - ); - } - return result; - } - - remove(onComplete?: (a: Error | null) => void): Promise { - validateArgCount('Reference.remove', 0, 1, arguments.length); - validateCallback('Reference.remove', 'onComplete', onComplete, true); - - const result = remove(this._delegate); - if (onComplete) { - result.then( - () => onComplete(null), - error => onComplete(error) - ); - } - return result; - } - - transaction( - transactionUpdate: (currentData: unknown) => unknown, - onComplete?: ( - error: Error | null, - committed: boolean, - dataSnapshot: DataSnapshot | null - ) => void, - applyLocally?: boolean - ): Promise { - validateArgCount('Reference.transaction', 1, 3, arguments.length); - validateCallback( - 'Reference.transaction', - 'transactionUpdate', - transactionUpdate, - false - ); - validateCallback('Reference.transaction', 'onComplete', onComplete, true); - validateBoolean( - 'Reference.transaction', - 'applyLocally', - applyLocally, - true - ); - - const result = runTransaction(this._delegate, transactionUpdate, { - applyLocally - }).then( - transactionResult => - new TransactionResult( - transactionResult.committed, - new DataSnapshot(this.database, transactionResult.snapshot) - ) - ); - if (onComplete) { - result.then( - transactionResult => - onComplete( - null, - transactionResult.committed, - transactionResult.snapshot - ), - error => onComplete(error, false, null) - ); - } - return result; - } - - setPriority( - priority: string | number | null, - onComplete?: (a: Error | null) => void - ): Promise { - validateArgCount('Reference.setPriority', 1, 2, arguments.length); - validateCallback('Reference.setPriority', 'onComplete', onComplete, true); - - const result = setPriority(this._delegate, priority); - if (onComplete) { - result.then( - () => onComplete(null), - error => onComplete(error) - ); - } - return result; - } - - push(value?: unknown, onComplete?: (a: Error | null) => void): Reference { - validateArgCount('Reference.push', 0, 2, arguments.length); - validateCallback('Reference.push', 'onComplete', onComplete, true); - - const expPromise = push(this._delegate, value) as ThenableReferenceImpl; - const promise = expPromise.then( - expRef => new Reference(this.database, expRef) - ); - - if (onComplete) { - promise.then( - () => onComplete(null), - error => onComplete(error) - ); - } - - const result = new Reference(this.database, expPromise); - result.then = promise.then.bind(promise); - result.catch = promise.catch.bind(promise, undefined); - return result; - } + /** The root `DatabaseReference` of the Database. */ + readonly root: DatabaseReference; +} - onDisconnect(): OnDisconnect { - validateWritablePath('Reference.onDisconnect', this._delegate._path); - return new OnDisconnect( - new ExpOnDisconnect(this._delegate._repo, this._delegate._path) - ); - } +/** + * A `Promise` that can also act as a `DatabaseReference` when returned by + * {@link push}. The reference is available immediately and the `Promise` resolves + * as the write to the backend completes. + */ +export interface ThenableReference + extends DatabaseReference, + Pick, 'then' | 'catch'> {} - get key(): string | null { - return this.getKey(); - } +/** A callback that can invoked to remove a listener. */ +export type Unsubscribe = () => void; - get parent(): Reference | null { - return this.getParent(); - } +/** An options objects that can be used to customize a listener. */ +export interface ListenOptions { + /** Whether to remove the listener after its first invocation. */ + readonly onlyOnce?: boolean; +} - get root(): Reference { - return this.getRoot(); - } +export interface ReferenceConstructor { + new (repo: Repo, path: Path): DatabaseReference; } diff --git a/packages/database/src/exp/Reference_impl.ts b/packages/database/src/api/Reference_impl.ts similarity index 100% rename from packages/database/src/exp/Reference_impl.ts rename to packages/database/src/api/Reference_impl.ts diff --git a/packages/database/src/exp/ServerValue.ts b/packages/database/src/api/ServerValue.ts similarity index 100% rename from packages/database/src/exp/ServerValue.ts rename to packages/database/src/api/ServerValue.ts diff --git a/packages/database/src/exp/Transaction.ts b/packages/database/src/api/Transaction.ts similarity index 100% rename from packages/database/src/exp/Transaction.ts rename to packages/database/src/api/Transaction.ts diff --git a/packages/database/src/api/test_access.ts b/packages/database/src/api/test_access.ts index 9e523158e10..2a4872eb3fc 100644 --- a/packages/database/src/api/test_access.ts +++ b/packages/database/src/api/test_access.ts @@ -17,10 +17,9 @@ import { PersistentConnection } from '../core/PersistentConnection'; import { RepoInfo } from '../core/RepoInfo'; -import { repoManagerForceRestClient } from '../exp/Database'; import { Connection } from '../realtime/Connection'; -import { Query } from './Reference'; +import { repoManagerForceRestClient } from './Database'; export const DataConnection = PersistentConnection; @@ -43,6 +42,9 @@ export const DataConnection = PersistentConnection; // RealTimeConnection properties that we use in tests. export const RealTimeConnection = Connection; +/** + * @internal + */ export const hijackHash = function (newHash: () => string) { const oldPut = PersistentConnection.prototype.put; PersistentConnection.prototype.put = function ( @@ -63,12 +65,9 @@ export const hijackHash = function (newHash: () => string) { export const ConnectionTarget = RepoInfo; -export const queryIdentifier = function (query: Query) { - return query._delegate._queryIdentifier; -}; - /** * Forces the RepoManager to create Repos that use ReadonlyRestClient instead of PersistentConnection. + * @internal */ export const forceRestClient = function (forceRestClient: boolean) { repoManagerForceRestClient(forceRestClient); diff --git a/packages/database/src/core/SyncPoint.ts b/packages/database/src/core/SyncPoint.ts index 6751b2242a8..39e65770142 100644 --- a/packages/database/src/core/SyncPoint.ts +++ b/packages/database/src/core/SyncPoint.ts @@ -17,7 +17,7 @@ import { assert } from '@firebase/util'; -import { ReferenceConstructor } from '../exp/Reference'; +import { ReferenceConstructor } from '../api/Reference'; import { Operation } from './operation/Operation'; import { ChildrenNode } from './snap/ChildrenNode'; diff --git a/packages/database/src/core/SyncTree.ts b/packages/database/src/core/SyncTree.ts index 0fad43f5976..a2d0888f03c 100644 --- a/packages/database/src/core/SyncTree.ts +++ b/packages/database/src/core/SyncTree.ts @@ -17,7 +17,7 @@ import { assert } from '@firebase/util'; -import { ReferenceConstructor } from '../exp/Reference'; +import { ReferenceConstructor } from '../api/Reference'; import { AckUserWrite } from './operation/AckUserWrite'; import { ListenComplete } from './operation/ListenComplete'; @@ -831,9 +831,10 @@ function syncTreeQueryKeyForTag_( /** * Given a queryKey (created by makeQueryKey), parse it back into a path and queryId. */ -function syncTreeParseQueryKey_( - queryKey: string -): { queryId: string; path: Path } { +function syncTreeParseQueryKey_(queryKey: string): { + queryId: string; + path: Path; +} { const splitIndex = queryKey.indexOf('$'); assert( splitIndex !== -1 && splitIndex < queryKey.length - 1, diff --git a/packages/database/src/core/snap/ChildrenNode.ts b/packages/database/src/core/snap/ChildrenNode.ts index fce34e484c3..f3ec7f3b109 100644 --- a/packages/database/src/core/snap/ChildrenNode.ts +++ b/packages/database/src/core/snap/ChildrenNode.ts @@ -219,7 +219,7 @@ export class ChildrenNode implements Node { const array: unknown[] = []; // eslint-disable-next-line guard-for-in for (const key in obj) { - array[(key as unknown) as number] = obj[key]; + array[key as unknown as number] = obj[key]; } return array; diff --git a/packages/database/src/core/snap/childSet.ts b/packages/database/src/core/snap/childSet.ts index bef2b193f1f..7fd5d0c36ce 100644 --- a/packages/database/src/core/snap/childSet.ts +++ b/packages/database/src/core/snap/childSet.ts @@ -77,10 +77,10 @@ export const buildChildSet = function ( return null; } else if (length === 1) { namedNode = childList[low]; - key = keyFn ? keyFn(namedNode) : ((namedNode as unknown) as K); + key = keyFn ? keyFn(namedNode) : (namedNode as unknown as K); return new LLRBNode( key, - (namedNode.node as unknown) as V, + namedNode.node as unknown as V, LLRBNode.BLACK, null, null @@ -91,10 +91,10 @@ export const buildChildSet = function ( const left = buildBalancedTree(low, middle); const right = buildBalancedTree(middle + 1, high); namedNode = childList[middle]; - key = keyFn ? keyFn(namedNode) : ((namedNode as unknown) as K); + key = keyFn ? keyFn(namedNode) : (namedNode as unknown as K); return new LLRBNode( key, - (namedNode.node as unknown) as V, + namedNode.node as unknown as V, LLRBNode.BLACK, left, right @@ -113,11 +113,11 @@ export const buildChildSet = function ( index -= chunkSize; const childTree = buildBalancedTree(low + 1, high); const namedNode = childList[low]; - const key: K = keyFn ? keyFn(namedNode) : ((namedNode as unknown) as K); + const key: K = keyFn ? keyFn(namedNode) : (namedNode as unknown as K); attachPennant( new LLRBNode( key, - (namedNode.node as unknown) as V, + namedNode.node as unknown as V, color, null, childTree diff --git a/packages/database/src/core/util/ImmutableTree.ts b/packages/database/src/core/util/ImmutableTree.ts index a6449878dad..cac9d0d4c9c 100644 --- a/packages/database/src/core/util/ImmutableTree.ts +++ b/packages/database/src/core/util/ImmutableTree.ts @@ -91,10 +91,11 @@ export class ImmutableTree { const front = pathGetFront(relativePath); const child = this.children.get(front); if (child !== null) { - const childExistingPathAndValue = child.findRootMostMatchingPathAndValue( - pathPopFront(relativePath), - predicate - ); + const childExistingPathAndValue = + child.findRootMostMatchingPathAndValue( + pathPopFront(relativePath), + predicate + ); if (childExistingPathAndValue != null) { const fullPath = pathChild( new Path(front), diff --git a/packages/database/src/core/util/SortedMap.ts b/packages/database/src/core/util/SortedMap.ts index c27cd7d5226..fa5c6bb65d2 100644 --- a/packages/database/src/core/util/SortedMap.ts +++ b/packages/database/src/core/util/SortedMap.ts @@ -96,7 +96,7 @@ export class SortedMapIterator { if (this.resultGenerator_) { result = this.resultGenerator_(node.key, node.value); } else { - result = ({ key: node.key, value: node.value } as unknown) as T; + result = { key: node.key, value: node.value } as unknown as T; } if (this.isReverse_) { @@ -129,7 +129,7 @@ export class SortedMapIterator { if (this.resultGenerator_) { return this.resultGenerator_(node.key, node.value); } else { - return ({ key: node.key, value: node.value } as unknown) as T; + return { key: node.key, value: node.value } as unknown as T; } } } diff --git a/packages/database/src/core/util/libs/parser.ts b/packages/database/src/core/util/libs/parser.ts index e4f006a635a..92be50daa2f 100644 --- a/packages/database/src/core/util/libs/parser.ts +++ b/packages/database/src/core/util/libs/parser.ts @@ -101,9 +101,7 @@ export const parseRepoInfo = function ( }; }; -export const parseDatabaseURL = function ( - dataURL: string -): { +export const parseDatabaseURL = function (dataURL: string): { host: string; port: number; domain: string; diff --git a/packages/database/src/core/util/util.ts b/packages/database/src/core/util/util.ts index 4f6a067efb1..4ce187b755c 100644 --- a/packages/database/src/core/util/util.ts +++ b/packages/database/src/core/util/util.ts @@ -26,14 +26,8 @@ import { } from '@firebase/util'; import { SessionStorage } from '../storage/storage'; +import { QueryContext } from '../view/EventRegistration'; -// TODO: revert to import { QueryContext } from '../view/EventRegistration'; once the modular SDK goes GA -/** - * This is part of a workaround for an issue in the no-modular '@firebase/database' where its typings - * reference types from `@firebase/app-exp`. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type QueryContext = any; declare const window: Window; // eslint-disable-next-line @typescript-eslint/no-explicit-any declare const Windows: any; diff --git a/packages/database/src/core/util/validation.ts b/packages/database/src/core/util/validation.ts index 356f03f3ac2..073b99fe844 100644 --- a/packages/database/src/core/util/validation.ts +++ b/packages/database/src/core/util/validation.ts @@ -315,31 +315,6 @@ export const validatePriority = function ( } }; -export const validateEventType = function ( - fnName: string, - eventType: string, - optional: boolean -) { - if (optional && eventType === undefined) { - return; - } - - switch (eventType) { - case 'value': - case 'child_added': - case 'child_removed': - case 'child_changed': - case 'child_moved': - break; - default: - throw new Error( - errorPrefixFxn(fnName, 'eventType') + - 'must be a valid event type = "value", "child_added", "child_removed", ' + - '"child_changed", or "child_moved".' - ); - } -}; - export const validateKey = function ( fnName: string, argumentName: string, @@ -360,6 +335,9 @@ export const validateKey = function ( } }; +/** + * @internal + */ export const validatePathString = function ( fnName: string, argumentName: string, @@ -395,6 +373,9 @@ export const validateRootPathString = function ( validatePathString(fnName, argumentName, pathString, optional); }; +/** + * @internal + */ export const validateWritablePath = function (fnName: string, path: Path) { if (pathGetFront(path) === '.info') { throw new Error(fnName + " failed = Can't modify data under /.info/"); @@ -422,22 +403,6 @@ export const validateUrl = function ( } }; -export const validateBoolean = function ( - fnName: string, - argumentName: string, - bool: unknown, - optional: boolean -) { - if (optional && bool === undefined) { - return; - } - if (typeof bool !== 'boolean') { - throw new Error( - errorPrefixFxn(fnName, argumentName) + 'must be a boolean.' - ); - } -}; - export const validateString = function ( fnName: string, argumentName: string, diff --git a/packages/database/src/core/version.ts b/packages/database/src/core/version.ts index d09ef1c244b..7c18e8c2949 100644 --- a/packages/database/src/core/version.ts +++ b/packages/database/src/core/version.ts @@ -18,7 +18,10 @@ /** The semver (www.semver.org) version of the SDK. */ export let SDK_VERSION = ''; -// SDK_VERSION should be set before any database instance is created +/** + * SDK_VERSION should be set before any database instance is created + * @internal + */ export function setSDKVersion(version: string): void { SDK_VERSION = version; } diff --git a/packages/database/src/core/view/Event.ts b/packages/database/src/core/view/Event.ts index 81d5d3b5d2e..26795a71124 100644 --- a/packages/database/src/core/view/Event.ts +++ b/packages/database/src/core/view/Event.ts @@ -17,7 +17,7 @@ import { stringify } from '@firebase/util'; -import { DataSnapshot as ExpDataSnapshot } from '../../exp/Reference_impl'; +import { DataSnapshot as ExpDataSnapshot } from '../../api/Reference_impl'; import { Path } from '../util/Path'; import { EventRegistration } from './EventRegistration'; diff --git a/packages/database/src/core/view/EventRegistration.ts b/packages/database/src/core/view/EventRegistration.ts index 1a814163e24..bbe18e49d1c 100644 --- a/packages/database/src/core/view/EventRegistration.ts +++ b/packages/database/src/core/view/EventRegistration.ts @@ -17,7 +17,7 @@ import { assert } from '@firebase/util'; -import { DataSnapshot } from '../../exp/Reference_impl'; +import { DataSnapshot } from '../../api/Reference_impl'; import { Repo } from '../Repo'; import { Path } from '../util/Path'; @@ -30,6 +30,8 @@ import { QueryParams } from './QueryParams'; * to the original user-issued callbacks, which allows equality * comparison by reference even though this callbacks are wrapped before * they can be passed to the firebase@exp SDK. + * + * @internal */ export interface UserCallback { (dataSnapshot: DataSnapshot, previousChildName?: string | null): unknown; diff --git a/packages/database/src/core/view/QueryParams.ts b/packages/database/src/core/view/QueryParams.ts index 577970fb255..4deebec0531 100644 --- a/packages/database/src/core/view/QueryParams.ts +++ b/packages/database/src/core/view/QueryParams.ts @@ -63,6 +63,8 @@ const enum REST_QUERY_CONSTANTS { * This class is an immutable-from-the-public-api struct containing a set of query parameters defining a * range to be returned for a particular location. It is assumed that validation of parameters is done at the * user-facing API level, so it is not done here. + * + * @internal */ export class QueryParams { limitSet_ = false; diff --git a/packages/database/src/core/view/ViewProcessor.ts b/packages/database/src/core/view/ViewProcessor.ts index 2739815fcbd..5011d192b7a 100644 --- a/packages/database/src/core/view/ViewProcessor.ts +++ b/packages/database/src/core/view/ViewProcessor.ts @@ -297,12 +297,13 @@ function viewProcessorGenerateEventCacheAfterServerEvent( let newEventChild; if (oldEventSnap.isCompleteForChild(childKey)) { serverNode = viewCache.serverCache.getNode(); - const eventChildUpdate = writeTreeRefCalcEventCacheAfterServerOverwrite( - writesCache, - changePath, - oldEventSnap.getNode(), - serverNode - ); + const eventChildUpdate = + writeTreeRefCalcEventCacheAfterServerOverwrite( + writesCache, + changePath, + oldEventSnap.getNode(), + serverNode + ); if (eventChildUpdate != null) { newEventChild = oldEventSnap .getNode() diff --git a/packages/database/src/exp/Database.ts b/packages/database/src/exp/Database.ts deleted file mode 100644 index d59ca53e191..00000000000 --- a/packages/database/src/exp/Database.ts +++ /dev/null @@ -1,408 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { - _FirebaseService, - _getProvider, - FirebaseApp, - getApp -} from '@firebase/app-exp'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { Provider } from '@firebase/component'; -import { - getModularInstance, - createMockUserToken, - EmulatorMockTokenOptions -} from '@firebase/util'; - -import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider'; -import { - AuthTokenProvider, - EmulatorTokenProvider, - FirebaseAuthTokenProvider -} from '../core/AuthTokenProvider'; -import { Repo, repoInterrupt, repoResume, repoStart } from '../core/Repo'; -import { RepoInfo } from '../core/RepoInfo'; -import { parseRepoInfo } from '../core/util/libs/parser'; -import { newEmptyPath, pathIsEmpty } from '../core/util/Path'; -import { - fatal, - log, - enableLogging as enableLoggingImpl -} from '../core/util/util'; -import { validateUrl } from '../core/util/validation'; - -import { ReferenceImpl } from './Reference_impl'; - -/** - * This variable is also defined in the firebase Node.js Admin SDK. Before - * modifying this definition, consult the definition in: - * - * https://github.com/firebase/firebase-admin-node - * - * and make sure the two are consistent. - */ -const FIREBASE_DATABASE_EMULATOR_HOST_VAR = 'FIREBASE_DATABASE_EMULATOR_HOST'; - -/** - * Creates and caches `Repo` instances. - */ -const repos: { - [appName: string]: { - [dbUrl: string]: Repo; - }; -} = {}; - -/** - * If true, any new `Repo` will be created to use `ReadonlyRestClient` (for testing purposes). - */ -let useRestClient = false; - -/** - * Update an existing `Repo` in place to point to a new host/port. - */ -function repoManagerApplyEmulatorSettings( - repo: Repo, - host: string, - port: number, - tokenProvider?: AuthTokenProvider -): void { - repo.repoInfo_ = new RepoInfo( - `${host}:${port}`, - /* secure= */ false, - repo.repoInfo_.namespace, - repo.repoInfo_.webSocketOnly, - repo.repoInfo_.nodeAdmin, - repo.repoInfo_.persistenceKey, - repo.repoInfo_.includeNamespaceInQueryParams - ); - - if (tokenProvider) { - repo.authTokenProvider_ = tokenProvider; - } -} - -/** - * This function should only ever be called to CREATE a new database instance. - * @internal - */ -export function repoManagerDatabaseFromApp( - app: FirebaseApp, - authProvider: Provider, - appCheckProvider?: Provider, - url?: string, - nodeAdmin?: boolean -): Database { - let dbUrl: string | undefined = url || app.options.databaseURL; - if (dbUrl === undefined) { - if (!app.options.projectId) { - fatal( - "Can't determine Firebase Database URL. Be sure to include " + - ' a Project ID when calling firebase.initializeApp().' - ); - } - - log('Using default host for project ', app.options.projectId); - dbUrl = `${app.options.projectId}-default-rtdb.firebaseio.com`; - } - - let parsedUrl = parseRepoInfo(dbUrl, nodeAdmin); - let repoInfo = parsedUrl.repoInfo; - - let isEmulator: boolean; - - let dbEmulatorHost: string | undefined = undefined; - if (typeof process !== 'undefined') { - dbEmulatorHost = process.env[FIREBASE_DATABASE_EMULATOR_HOST_VAR]; - } - - if (dbEmulatorHost) { - isEmulator = true; - dbUrl = `http://${dbEmulatorHost}?ns=${repoInfo.namespace}`; - parsedUrl = parseRepoInfo(dbUrl, nodeAdmin); - repoInfo = parsedUrl.repoInfo; - } else { - isEmulator = !parsedUrl.repoInfo.secure; - } - - const authTokenProvider = - nodeAdmin && isEmulator - ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER) - : new FirebaseAuthTokenProvider(app.name, app.options, authProvider); - - validateUrl('Invalid Firebase Database URL', parsedUrl); - if (!pathIsEmpty(parsedUrl.path)) { - fatal( - 'Database URL must point to the root of a Firebase Database ' + - '(not including a child path).' - ); - } - - const repo = repoManagerCreateRepo( - repoInfo, - app, - authTokenProvider, - new AppCheckTokenProvider(app.name, appCheckProvider) - ); - return new Database(repo, app); -} - -/** - * Remove the repo and make sure it is disconnected. - * - */ -function repoManagerDeleteRepo(repo: Repo, appName: string): void { - const appRepos = repos[appName]; - // This should never happen... - if (!appRepos || appRepos[repo.key] !== repo) { - fatal(`Database ${appName}(${repo.repoInfo_}) has already been deleted.`); - } - repoInterrupt(repo); - delete appRepos[repo.key]; -} - -/** - * Ensures a repo doesn't already exist and then creates one using the - * provided app. - * - * @param repoInfo - The metadata about the Repo - * @returns The Repo object for the specified server / repoName. - */ -function repoManagerCreateRepo( - repoInfo: RepoInfo, - app: FirebaseApp, - authTokenProvider: AuthTokenProvider, - appCheckProvider: AppCheckTokenProvider -): Repo { - let appRepos = repos[app.name]; - - if (!appRepos) { - appRepos = {}; - repos[app.name] = appRepos; - } - - let repo = appRepos[repoInfo.toURLString()]; - if (repo) { - fatal( - 'Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.' - ); - } - repo = new Repo(repoInfo, useRestClient, authTokenProvider, appCheckProvider); - appRepos[repoInfo.toURLString()] = repo; - - return repo; -} - -/** - * Forces us to use ReadonlyRestClient instead of PersistentConnection for new Repos. - */ -export function repoManagerForceRestClient(forceRestClient: boolean): void { - useRestClient = forceRestClient; -} - -/** - * Class representing a Firebase Realtime Database. - */ -export class Database implements _FirebaseService { - /** Represents a `Database` instance. */ - readonly 'type' = 'database'; - - /** Track if the instance has been used (root or repo accessed) */ - _instanceStarted: boolean = false; - - /** Backing state for root_ */ - private _rootInternal?: ReferenceImpl; - - /** @hideconstructor */ - constructor( - public _repoInternal: Repo, - /** The {@link @firebase/app#FirebaseApp} associated with this Realtime Database instance. */ - readonly app: FirebaseApp - ) {} - - get _repo(): Repo { - if (!this._instanceStarted) { - repoStart( - this._repoInternal, - this.app.options.appId, - this.app.options['databaseAuthVariableOverride'] - ); - this._instanceStarted = true; - } - return this._repoInternal; - } - - get _root(): ReferenceImpl { - if (!this._rootInternal) { - this._rootInternal = new ReferenceImpl(this._repo, newEmptyPath()); - } - return this._rootInternal; - } - - _delete(): Promise { - if (this._rootInternal !== null) { - repoManagerDeleteRepo(this._repo, this.app.name); - this._repoInternal = null; - this._rootInternal = null; - } - return Promise.resolve(); - } - - _checkNotDeleted(apiName: string) { - if (this._rootInternal === null) { - fatal('Cannot call ' + apiName + ' on a deleted database.'); - } - } -} - -/** - * Returns the instance of the Realtime Database SDK that is associated - * with the provided {@link @firebase/app#FirebaseApp}. Initializes a new instance with - * with default settings if no instance exists or if the existing instance uses - * a custom database URL. - * - * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned Realtime - * Database instance is associated with. - * @param url - The URL of the Realtime Database instance to connect to. If not - * provided, the SDK connects to the default instance of the Firebase App. - * @returns The `Database` instance of the provided app. - */ -export function getDatabase( - app: FirebaseApp = getApp(), - url?: string -): Database { - return _getProvider(app, 'database-exp').getImmediate({ - identifier: url - }) as Database; -} - -/** - * Modify the provided instance to communicate with the Realtime Database - * emulator. - * - *

Note: This method must be called before performing any other operation. - * - * @param db - The instance to modify. - * @param host - The emulator host (ex: localhost) - * @param port - The emulator port (ex: 8080) - * @param options.mockUserToken - the mock auth token to use for unit testing Security Rules - */ -export function connectDatabaseEmulator( - db: Database, - host: string, - port: number, - options: { - mockUserToken?: EmulatorMockTokenOptions | string; - } = {} -): void { - db = getModularInstance(db); - db._checkNotDeleted('useEmulator'); - if (db._instanceStarted) { - fatal( - 'Cannot call useEmulator() after instance has already been initialized.' - ); - } - - const repo = db._repoInternal; - let tokenProvider: EmulatorTokenProvider | undefined = undefined; - if (repo.repoInfo_.nodeAdmin) { - if (options.mockUserToken) { - fatal( - 'mockUserToken is not supported by the Admin SDK. For client access with mock users, please use the "firebase" package instead of "firebase-admin".' - ); - } - tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER); - } else if (options.mockUserToken) { - const token = - typeof options.mockUserToken === 'string' - ? options.mockUserToken - : createMockUserToken(options.mockUserToken, db.app.options.projectId); - tokenProvider = new EmulatorTokenProvider(token); - } - - // Modify the repo to apply emulator settings - repoManagerApplyEmulatorSettings(repo, host, port, tokenProvider); -} - -/** - * Disconnects from the server (all Database operations will be completed - * offline). - * - * The client automatically maintains a persistent connection to the Database - * server, which will remain active indefinitely and reconnect when - * disconnected. However, the `goOffline()` and `goOnline()` methods may be used - * to control the client connection in cases where a persistent connection is - * undesirable. - * - * While offline, the client will no longer receive data updates from the - * Database. However, all Database operations performed locally will continue to - * immediately fire events, allowing your application to continue behaving - * normally. Additionally, each operation performed locally will automatically - * be queued and retried upon reconnection to the Database server. - * - * To reconnect to the Database and begin receiving remote events, see - * `goOnline()`. - * - * @param db - The instance to disconnect. - */ -export function goOffline(db: Database): void { - db = getModularInstance(db); - db._checkNotDeleted('goOffline'); - repoInterrupt(db._repo); -} - -/** - * Reconnects to the server and synchronizes the offline Database state - * with the server state. - * - * This method should be used after disabling the active connection with - * `goOffline()`. Once reconnected, the client will transmit the proper data - * and fire the appropriate events so that your client "catches up" - * automatically. - * - * @param db - The instance to reconnect. - */ -export function goOnline(db: Database): void { - db = getModularInstance(db); - db._checkNotDeleted('goOnline'); - repoResume(db._repo); -} - -/** - * Logs debugging information to the console. - * - * @param enabled - Enables logging if `true`, disables logging if `false`. - * @param persistent - Remembers the logging state between page refreshes if - * `true`. - */ -export function enableLogging(enabled: boolean, persistent?: boolean); - -/** - * Logs debugging information to the console. - * - * @param logger - A custom logger function to control how things get logged. - */ -export function enableLogging(logger: (message: string) => unknown); - -export function enableLogging( - logger: boolean | ((message: string) => unknown), - persistent?: boolean -): void { - enableLoggingImpl(logger, persistent); -} diff --git a/packages/database/src/exp/Reference.ts b/packages/database/src/exp/Reference.ts deleted file mode 100644 index c2e97aa229e..00000000000 --- a/packages/database/src/exp/Reference.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Repo } from '../core/Repo'; -import { Path } from '../core/util/Path'; -import { QueryContext } from '../core/view/EventRegistration'; - -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * A `Query` sorts and filters the data at a Database location so only a subset - * of the child data is included. This can be used to order a collection of - * data by some attribute (for example, height of dinosaurs) as well as to - * restrict a large list of items (for example, chat messages) down to a number - * suitable for synchronizing to the client. Queries are created by chaining - * together one or more of the filter methods defined here. - * - * Just as with a `DatabaseReference`, you can receive data from a `Query` by using the - * `on*()` methods. You will only receive events and `DataSnapshot`s for the - * subset of the data that matches your query. - * - * See {@link https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data} - * for more information. - */ -export interface Query extends QueryContext { - /** The `DatabaseReference` for the `Query`'s location. */ - readonly ref: DatabaseReference; - - /** - * Returns whether or not the current and provided queries represent the same - * location, have the same query parameters, and are from the same instance of - * `FirebaseApp`. - * - * Two `DatabaseReference` objects are equivalent if they represent the same location - * and are from the same instance of `FirebaseApp`. - * - * Two `Query` objects are equivalent if they represent the same location, - * have the same query parameters, and are from the same instance of - * `FirebaseApp`. Equivalent queries share the same sort order, limits, and - * starting and ending points. - * - * @param other - The query to compare against. - * @returns Whether or not the current and provided queries are equivalent. - */ - isEqual(other: Query | null): boolean; - - /** - * Returns a JSON-serializable representation of this object. - * - * @returns A JSON-serializable representation of this object. - */ - toJSON(): string; - - /** - * Gets the absolute URL for this location. - * - * The `toString()` method returns a URL that is ready to be put into a - * browser, curl command, or a `refFromURL()` call. Since all of those expect - * the URL to be url-encoded, `toString()` returns an encoded URL. - * - * Append '.json' to the returned URL when typed into a browser to download - * JSON-formatted data. If the location is secured (that is, not publicly - * readable), you will get a permission-denied error. - * - * @returns The absolute URL for this location. - */ - toString(): string; -} - -/** - * A `DatabaseReference` represents a specific location in your Database and can be used - * for reading or writing data to that Database location. - * - * You can reference the root or child location in your Database by calling - * `ref()` or `ref("child/path")`. - * - * Writing is done with the `set()` method and reading can be done with the - * `on*()` method. See {@link - * https://firebase.google.com/docs/database/web/read-and-write} - */ -export interface DatabaseReference extends Query { - /** - * The last part of the `DatabaseReference`'s path. - * - * For example, `"ada"` is the key for - * `https://.firebaseio.com/users/ada`. - * - * The key of a root `DatabaseReference` is `null`. - */ - readonly key: string | null; - - /** - * The parent location of a `DatabaseReference`. - * - * The parent of a root `DatabaseReference` is `null`. - */ - readonly parent: DatabaseReference | null; - - /** The root `DatabaseReference` of the Database. */ - readonly root: DatabaseReference; -} - -/** - * A `Promise` that can also act as a `DatabaseReference` when returned by - * {@link push}. The reference is available immediately and the `Promise` resolves - * as the write to the backend completes. - */ -export interface ThenableReference - extends DatabaseReference, - Pick, 'then' | 'catch'> {} - -/** A callback that can invoked to remove a listener. */ -export type Unsubscribe = () => void; - -/** An options objects that can be used to customize a listener. */ -export interface ListenOptions { - /** Whether to remove the listener after its first invocation. */ - readonly onlyOnce?: boolean; -} - -export interface ReferenceConstructor { - new (repo: Repo, path: Path): DatabaseReference; -} diff --git a/packages/database/exp/index.node.ts b/packages/database/src/index.node.ts similarity index 100% rename from packages/database/exp/index.node.ts rename to packages/database/src/index.node.ts diff --git a/packages/database/exp/index.ts b/packages/database/src/index.ts similarity index 83% rename from packages/database/exp/index.ts rename to packages/database/src/index.ts index 30c71177e81..2e763d6e36e 100644 --- a/packages/database/exp/index.ts +++ b/packages/database/src/index.ts @@ -21,8 +21,15 @@ * limitations under the License. */ +import { Database } from './api/Database'; import { registerDatabase } from './register'; export * from './api'; registerDatabase(); + +declare module '@firebase/component' { + interface NameServiceMapping { + 'database': Database; + } +} diff --git a/packages/database/src/realtime/BrowserPollConnection.ts b/packages/database/src/realtime/BrowserPollConnection.ts index c41a9fb97a1..9f2340417b5 100644 --- a/packages/database/src/realtime/BrowserPollConnection.ts +++ b/packages/database/src/realtime/BrowserPollConnection.ts @@ -209,9 +209,8 @@ export class BrowserPollConnection implements Transport { Math.random() * 100000000 ); if (this.scriptTagHolder.uniqueCallbackIdentifier) { - urlParams[ - FIREBASE_LONGPOLL_CALLBACK_ID_PARAM - ] = this.scriptTagHolder.uniqueCallbackIdentifier; + urlParams[FIREBASE_LONGPOLL_CALLBACK_ID_PARAM] = + this.scriptTagHolder.uniqueCallbackIdentifier; } urlParams[VERSION_PARAM] = PROTOCOL_VERSION; if (this.transportSessionId) { @@ -456,9 +455,8 @@ export class FirebaseIFrameScriptHolder { window[ FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier ] = commandCB; - window[ - FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier - ] = onMessageCB; + window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] = + onMessageCB; //Create an iframe for us to add script tags to. this.myIFrame = FirebaseIFrameScriptHolder.createIFrame_(); @@ -721,18 +719,19 @@ export class FirebaseIFrameScriptHolder { newScript.async = true; newScript.src = url; // eslint-disable-next-line @typescript-eslint/no-explicit-any - newScript.onload = (newScript as any).onreadystatechange = function () { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const rstate = (newScript as any).readyState; - if (!rstate || rstate === 'loaded' || rstate === 'complete') { + newScript.onload = (newScript as any).onreadystatechange = + function () { // eslint-disable-next-line @typescript-eslint/no-explicit-any - newScript.onload = (newScript as any).onreadystatechange = null; - if (newScript.parentNode) { - newScript.parentNode.removeChild(newScript); + const rstate = (newScript as any).readyState; + if (!rstate || rstate === 'loaded' || rstate === 'complete') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + newScript.onload = (newScript as any).onreadystatechange = null; + if (newScript.parentNode) { + newScript.parentNode.removeChild(newScript); + } + loadCB(); } - loadCB(); - } - }; + }; newScript.onerror = () => { log('Long-poll script failed to load: ' + url); this.sendNewPolls = false; diff --git a/packages/database/src/realtime/Constants.ts b/packages/database/src/realtime/Constants.ts index 4dba53f75dc..f7bca227db3 100644 --- a/packages/database/src/realtime/Constants.ts +++ b/packages/database/src/realtime/Constants.ts @@ -27,7 +27,8 @@ export const FORGE_REF = 'f'; // Matches console.firebase.google.com, firebase-console-*.corp.google.com and // firebase.corp.google.com -export const FORGE_DOMAIN_RE = /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/; +export const FORGE_DOMAIN_RE = + /(console\.firebase|firebase-console-\w+\.corp|firebase\.corp)\.google\.com/; export const LAST_SESSION_PARAM = 'ls'; diff --git a/packages/database/exp/register.ts b/packages/database/src/register.ts similarity index 82% rename from packages/database/exp/register.ts rename to packages/database/src/register.ts index 4f06e49727f..ee68bc252fe 100644 --- a/packages/database/exp/register.ts +++ b/packages/database/src/register.ts @@ -19,26 +19,21 @@ import { _registerComponent, registerVersion, SDK_VERSION -} from '@firebase/app-exp'; +} from '@firebase/app'; import { Component, ComponentType } from '@firebase/component'; import { name, version } from '../package.json'; import { setSDKVersion } from '../src/core/version'; -import { Database, repoManagerDatabaseFromApp } from '../src/exp/Database'; -declare module '@firebase/component' { - interface NameServiceMapping { - 'database-exp': Database; - } -} +import { repoManagerDatabaseFromApp } from './api/Database'; export function registerDatabase(variant?: string): void { setSDKVersion(SDK_VERSION); _registerComponent( new Component( - 'database-exp', + 'database', (container, { instanceIdentifier: url }) => { - const app = container.getProvider('app-exp').getImmediate()!; + const app = container.getProvider('app').getImmediate()!; const authProvider = container.getProvider('auth-internal'); const appCheckProvider = container.getProvider('app-check-internal'); return repoManagerDatabaseFromApp( diff --git a/packages/database/test/exp/integration.test.ts b/packages/database/test/exp/integration.test.ts index c2e276d8794..0d0858c28c8 100644 --- a/packages/database/test/exp/integration.test.ts +++ b/packages/database/test/exp/integration.test.ts @@ -16,10 +16,11 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -import { initializeApp, deleteApp } from '@firebase/app-exp'; +import { initializeApp, deleteApp } from '@firebase/app'; import { Deferred } from '@firebase/util'; import { expect } from 'chai'; +import { onValue, set } from '../../src/api/Reference_impl'; import { get, getDatabase, @@ -29,8 +30,7 @@ import { ref, refFromURL, runTransaction -} from '../../exp/index'; -import { onValue, set } from '../../src/exp/Reference_impl'; +} from '../../src/index'; import { EventAccumulatorFactory } from '../helpers/EventAccumulator'; import { DATABASE_ADDRESS, DATABASE_URL } from '../helpers/util'; diff --git a/packages/database/test/helpers/util.ts b/packages/database/test/helpers/util.ts index ec2a0a8f733..fc27e9afcd3 100644 --- a/packages/database/test/helpers/util.ts +++ b/packages/database/test/helpers/util.ts @@ -1,38 +1,9 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -declare let MozWebSocket: WebSocket; - -import '../../index'; - -import firebase from '@firebase/app'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { Component, ComponentType } from '@firebase/component'; - -import { Query, Reference } from '../../src/api/Reference'; import { ConnectionTarget } from '../../src/api/test_access'; -import { Path } from '../../src/core/util/Path'; // eslint-disable-next-line @typescript-eslint/no-require-imports export const TEST_PROJECT = require('../../../../config/project.json'); - const EMULATOR_PORT = process.env.RTDB_EMULATOR_PORT; const EMULATOR_NAMESPACE = process.env.RTDB_EMULATOR_NAMESPACE; - const USE_EMULATOR = !!EMULATOR_PORT; /* @@ -51,144 +22,6 @@ export const DATABASE_URL = USE_EMULATOR ? `${DATABASE_ADDRESS}?ns=${EMULATOR_NAMESPACE}` : TEST_PROJECT.databaseURL; -console.log(`USE_EMULATOR: ${USE_EMULATOR}. DATABASE_URL: ${DATABASE_URL}.`); - -let numDatabases = 0; - -// mock authentication functions for testing -(firebase as _FirebaseNamespace).INTERNAL.registerComponent( - new Component( - 'auth-internal', - () => ({ - getToken: async () => null, - addAuthTokenListener: () => {}, - removeAuthTokenListener: () => {}, - getUid: () => null - }), - ComponentType.PRIVATE - ) -); - -export function createTestApp() { - const app = firebase.initializeApp({ databaseURL: DATABASE_URL }); - return app; -} - -/** - * Gets or creates a root node to the test namespace. All calls sharing the - * value of opt_i will share an app context. - */ -export function getRootNode(i = 0, ref?: string) { - if (i + 1 > numDatabases) { - numDatabases = i + 1; - } - let app; - try { - app = firebase.app('TEST-' + i); - } catch (e) { - app = firebase.initializeApp({ databaseURL: DATABASE_URL }, 'TEST-' + i); - } - const db = app.database(); - return db.ref(ref); -} - -/** - * Create multiple refs to the same top level - * push key - each on it's own Firebase.Context. - */ -export function getRandomNode(numNodes?): Reference | Reference[] { - if (numNodes === undefined) { - return getRandomNode(1)[0] as Reference; - } - - let child; - const nodeList = []; - for (let i = 0; i < numNodes; i++) { - const ref = getRootNode(i); - if (child === undefined) { - child = ref.push().key; - } - - nodeList[i] = ref.child(child); - } - - return nodeList as Reference[]; -} - -export function getQueryValue(query: Query) { - return query.once('value').then(snap => snap.val()); -} - -export function pause(milliseconds: number) { - return new Promise(resolve => { - setTimeout(() => resolve(), milliseconds); - }); -} - -export function getPath(query: Query) { - return query.toString().replace(DATABASE_ADDRESS, ''); -} - -export function shuffle(arr, randFn = Math.random) { - for (let i = arr.length - 1; i > 0; i--) { - const j = Math.floor(randFn() * (i + 1)); - const tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; - } -} - -let freshRepoId = 1; -const activeFreshApps = []; - -export function getFreshRepo(path: Path) { - const app = firebase.initializeApp( - { databaseURL: DATABASE_URL }, - 'ISOLATED_REPO_' + freshRepoId++ - ); - activeFreshApps.push(app); - return (app as any).database().ref(path.toString()); -} - -export function getFreshRepoFromReference(ref) { - const host = ref.root.toString(); - const path = ref.toString().replace(host, ''); - return getFreshRepo(path); -} - -// Little helpers to get the currently cached snapshot / value. -export function getSnap(path) { - let snap; - const callback = function (snapshot) { - snap = snapshot; - }; - path.once('value', callback); - return snap; -} - -export function getVal(path) { - const snap = getSnap(path); - return snap ? snap.val() : undefined; -} - -export function canCreateExtraConnections() { - return ( - typeof MozWebSocket !== 'undefined' || typeof WebSocket !== 'undefined' - ); -} - -export function buildObjFromKey(key) { - const keys = key.split('.'); - const obj = {}; - let parent = obj; - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - parent[key] = i < keys.length - 1 ? {} : 'test_value'; - parent = parent[key]; - } - return obj; -} - export function testRepoInfo(url) { const regex = /https?:\/\/(.*).firebaseio.com/; const match = url.match(regex); @@ -211,3 +44,12 @@ export function repoInfoForConnectionTest() { return testRepoInfo(TEST_PROJECT.databaseURL); } } + +export function shuffle(arr, randFn = Math.random) { + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(randFn() * (i + 1)); + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } +} diff --git a/packages-exp/app-exp/.eslintrc.js b/packages/firebase/.eslintrc.js similarity index 100% rename from packages-exp/app-exp/.eslintrc.js rename to packages/firebase/.eslintrc.js diff --git a/packages/firebase/README.md b/packages/firebase/README.md index 3ac98942788..0569dc7ea39 100644 --- a/packages/firebase/README.md +++ b/packages/firebase/README.md @@ -1,221 +1,5 @@ - - # Firebase - App success made simple ## Overview -[Firebase](https://firebase.google.com) provides the tools and infrastructure -you need to develop, grow, and earn money from your app. This package supports -web (browser), mobile-web, and server (Node.js) clients. - -For more information, visit: - -- [Firebase Realtime Database](https://firebase.google.com/docs/database/web/start) - - The Firebase Realtime Database lets you store and query user data, and makes - it available between users in realtime. -- [Cloud Firestore](https://firebase.google.com/docs/firestore/quickstart) - - Cloud Firestore is a flexible, scalable database for mobile, web, and server - development from Firebase and Google Cloud Platform. -- [Firebase Storage](https://firebase.google.com/docs/storage/web/start) - - Firebase Storage lets you upload and store user generated content, such as - files, and images. -- [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/js/client) - - Firebase Cloud Messaging is a cross-platform messaging solution that lets you - reliably deliver messages at no cost. -- [Firebase Authentication](https://firebase.google.com/docs/auth/web/manage-users) - - Firebase helps you authenticate and manage users who access your application. -- [Create and setup your account](https://firebase.google.com/docs/web/setup) - - Get started using Firebase for free. - -This SDK is intended for end-user client access from environments such as the -Web, mobile Web (e.g. React Native, Ionic), Node.js desktop (e.g. Electron), or -IoT devices running Node.js. If you are instead interested in using a Node.js -SDK which grants you admin access from a privileged environment (like a server), -you should use the -[Firebase Admin Node.js SDK](https://firebase.google.com/docs/admin/setup/). - -## Get the code (browser) - -We recommend only installing the features you need. The individually installable services are: - -- `firebase-app` - The core `firebase` client (required). -- `firebase-app-check` - Firebase App Check (optional). -- `firebase-analytics` - Firebase Analytics (optional). -- `firebase-auth` - Firebase Authentication (optional). -- `firebase-database` - The Firebase Realtime Database (optional). -- `firebase-firestore` - Cloud Firestore (optional). -- `firebase-storage` - Firebase Storage (optional). -- `firebase-messaging` - Firebase Cloud Messaging (optional). -- `firebase-functions` - Firebase Cloud Functions (optional). -- `firebase-remote-config` - Firebase Remote Config (optional). -- `firebase-performance` - Firebase Performance (optional). - -### Script include -Include Firebase in your web application via ` - - - - - - -``` - -_Note: To get a filled in version of the above code snippet, go to the -[Firebase console](https://console.firebase.google.com/) for your app and click on "Add -Firebase to your web app"._ - -#### Alternative - all-in-one import - ->This brings in all Firebase features. We recommend the method above to ->minimize download size by only including the scripts you need. - -Include Firebase in your web application via a ` - - -``` - -### NPM Bundler (Browserify, Webpack, Rollup, etc.) - -Install the Firebase NPM module: -``` -$ npm init -$ npm install --save firebase -``` - -In your code, you can import the services you use: - -```js -// This import loads the firebase namespace along with all its type information. -import firebase from 'firebase/app'; - -// These imports load individual services into the firebase namespace. -import 'firebase/auth'; -import 'firebase/database'; -``` - -Or if using `require()`: - -_Use the `.default` import from `firebase/app` in order for -typings to work correctly. -See [release notes for 8.0.0](https://firebase.google.com/support/release-notes/js#version_800_-_october_26_2020)._ - -```js -const firebase = require('firebase/app').default; -require('firebase/auth'); -require('firebase/database'); - -const app = firebase.initializeApp({ ... }); -``` - -_The type information from the import statement will include all of the SDKs, -not just the ones you have `required`, so you could get a runtime error if you -reference a non-required service._ - -#### Alternative - all-in-one import - ->This brings in all Firebase features. We recommend the method above to ->minimize download size by only including the scripts you need. - -```js -// This import loads all Firebase services, whether used in your code or not. -import firebase from 'firebase'; -``` - -Or with `require()`: - -```js -// This import loads all Firebase services, whether used in your code or not. -const firebase = require('firebase').default; -``` - -## Get the code (Node.js - server and command line) - -### NPM - -While you can write entire Firebase applications without any backend code, many -developers want to write server applications or command-line utilities using the -Node.js JavaScript runtime. - -You can use the same npm module to use Firebase in the Node.js runtime (on a -server or running from the command line): - -``` -$ npm init -$ npm install --save firebase -``` - -In your code, you can access Firebase using: - -```js -const firebase = require('firebase/app').default; -require('firebase/auth'); -require('firebase/database'); -const app = firebase.initializeApp({ ... }); -// ... -``` - -If you are using native ES6 module with --experimental-modules flag (or Node 12+) -you should do: - -```js -// This import loads the firebase namespace. -import firebase from 'firebase/app'; - -// These imports load individual services into the firebase namespace. -import 'firebase/auth'; -import 'firebase/database'; -``` - -_Known issue for typescript users with --experimental-modules: you have to set allowSyntheticDefaultImports to true in tsconfig.json to pass the type check. Use it with caution since it makes the assumption that all modules have a default export, which might not be the case for the other dependencies you have. And Your code will break if you try to import the default export from a module that doesn't have default export._ - -Firebase Cloud Messaging is not included in the server side Firebase npm module. -Instead, you can use the -[Firebase Cloud Messaging Rest API](https://firebase.google.com/docs/cloud-messaging/send-message). - -## API definition - -If you use the -[Closure Compiler](https://developers.google.com/closure/compiler/) or -compatible IDE, you can find API definitions for all the Firebase JavaScript API -in the included `/externs` directory in this package: - -``` -externs/ - firebase-app-externs.js - firebase-auth-externs.js - firebase-database-externs.js - firebase-firestore-externs.js - firebase-storage-externs.js - firebase-messaging-externs.js -``` - -## Changelog - -The Firebase changelog can be found at -[firebase.google.com](https://firebase.google.com/support/release-notes/js). - -## Browser/environment compatibility - -Please see [Environment Support](https://firebase.google.com/support/guides/environments_js-sdk). +TODO \ No newline at end of file diff --git a/packages/firebase/analytics/index.ts b/packages/firebase/analytics/index.ts index e620f611e03..b32e63c69c7 100644 --- a/packages/firebase/analytics/index.ts +++ b/packages/firebase/analytics/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -import '@firebase/analytics'; +export * from '@firebase/analytics'; diff --git a/packages/firebase/analytics/package.json b/packages/firebase/analytics/package.json index 1c0ebd1b76a..bcfe70fc0d2 100644 --- a/packages/firebase/analytics/package.json +++ b/packages/firebase/analytics/package.json @@ -1,6 +1,7 @@ { "name": "firebase/analytics", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/analytics/index.d.ts" } diff --git a/packages/firebase/app-check/index.ts b/packages/firebase/app-check/index.ts index 78ceba036c3..40e9af41078 100644 --- a/packages/firebase/app-check/index.ts +++ b/packages/firebase/app-check/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -import '@firebase/app-check'; +export * from '@firebase/app-check'; diff --git a/packages/firebase/app-check/package.json b/packages/firebase/app-check/package.json index 2d7360d675a..b9a0a2ac09d 100644 --- a/packages/firebase/app-check/package.json +++ b/packages/firebase/app-check/package.json @@ -1,6 +1,7 @@ { "name": "firebase/app-check", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/app-check/index.d.ts" } diff --git a/packages-exp/firebase-exp/app/index.cdn.ts b/packages/firebase/app/index.cdn.ts similarity index 88% rename from packages-exp/firebase-exp/app/index.cdn.ts rename to packages/firebase/app/index.cdn.ts index 008e95af754..84aa892fe69 100644 --- a/packages-exp/firebase-exp/app/index.cdn.ts +++ b/packages/firebase/app/index.cdn.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { registerVersion } from '@firebase/app-exp'; +import { registerVersion } from '@firebase/app'; import { name, version } from '../package.json'; registerVersion(name, version, 'cdn'); -export * from '@firebase/app-exp'; +export * from '@firebase/app'; diff --git a/packages/firebase/app/index.ts b/packages/firebase/app/index.ts index 90444a11b80..321395c7b0a 100644 --- a/packages/firebase/app/index.ts +++ b/packages/firebase/app/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import firebase from '@firebase/app'; +import { registerVersion } from '@firebase/app'; import { name, version } from '../package.json'; -firebase.registerVersion(name, version, 'app'); -firebase.SDK_VERSION = version; - -export default firebase; +registerVersion(name, version, 'app'); +export * from '@firebase/app'; diff --git a/packages/firebase/app/package.json b/packages/firebase/app/package.json index 06f2e67a295..8ef6c1b65f8 100644 --- a/packages/firebase/app/package.json +++ b/packages/firebase/app/package.json @@ -2,6 +2,6 @@ "name": "firebase/app", "main": "dist/index.cjs.js", "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "typings": "../index.d.ts" + "module": "dist/index.mjs", + "typings": "dist/app/index.d.ts" } diff --git a/packages-exp/firebase-exp/app-check/index.ts b/packages/firebase/auth/cordova/index.ts similarity index 93% rename from packages-exp/firebase-exp/app-check/index.ts rename to packages/firebase/auth/cordova/index.ts index 85c036a330d..e0575a0e787 100644 --- a/packages-exp/firebase-exp/app-check/index.ts +++ b/packages/firebase/auth/cordova/index.ts @@ -15,4 +15,4 @@ * limitations under the License. */ -export * from '@firebase/app-check-exp'; +export * from '@firebase/auth/cordova'; diff --git a/packages/firebase/auth/cordova/package.json b/packages/firebase/auth/cordova/package.json new file mode 100644 index 00000000000..07f80b5e21d --- /dev/null +++ b/packages/firebase/auth/cordova/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/auth/cordova", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/auth/cordova/index.d.ts" +} diff --git a/packages/firebase/auth/index.ts b/packages/firebase/auth/index.ts index 0e8cfbdebbb..49f5fa3b1d3 100644 --- a/packages/firebase/auth/index.ts +++ b/packages/firebase/auth/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,5 +14,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import '@firebase/auth'; +export * from '@firebase/auth'; diff --git a/packages/firebase/auth/package.json b/packages/firebase/auth/package.json index 2147199ca5f..0494c105c0c 100644 --- a/packages/firebase/auth/package.json +++ b/packages/firebase/auth/package.json @@ -1,6 +1,7 @@ { "name": "firebase/auth", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/auth/index.d.ts" } diff --git a/packages-exp/firebase-exp/auth/cordova/index.ts b/packages/firebase/auth/react-native/index.ts similarity index 93% rename from packages-exp/firebase-exp/auth/cordova/index.ts rename to packages/firebase/auth/react-native/index.ts index 4a6eba1206d..759132890fa 100644 --- a/packages-exp/firebase-exp/auth/cordova/index.ts +++ b/packages/firebase/auth/react-native/index.ts @@ -15,4 +15,4 @@ * limitations under the License. */ -export * from '@firebase/auth-exp/cordova'; +export * from '@firebase/auth/react-native'; diff --git a/packages/firebase/auth/react-native/package.json b/packages/firebase/auth/react-native/package.json new file mode 100644 index 00000000000..a7b7dd06927 --- /dev/null +++ b/packages/firebase/auth/react-native/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/auth/react-native", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/auth/react-native/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/analytics/index.ts b/packages/firebase/compat/analytics/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/analytics/index.ts rename to packages/firebase/compat/analytics/index.ts diff --git a/packages/firebase/compat/analytics/package.json b/packages/firebase/compat/analytics/package.json new file mode 100644 index 00000000000..b706bfd1fc8 --- /dev/null +++ b/packages/firebase/compat/analytics/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/analytics", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/analytics/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/app-check/index.ts b/packages/firebase/compat/app-check/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/app-check/index.ts rename to packages/firebase/compat/app-check/index.ts diff --git a/packages/firebase/compat/app-check/package.json b/packages/firebase/compat/app-check/package.json new file mode 100644 index 00000000000..86df945618e --- /dev/null +++ b/packages/firebase/compat/app-check/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/app-check", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/app-check/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/app/index.cdn.ts b/packages/firebase/compat/app/index.cdn.ts similarity index 100% rename from packages-exp/firebase-exp/compat/app/index.cdn.ts rename to packages/firebase/compat/app/index.cdn.ts diff --git a/packages-exp/firebase-exp/compat/app/index.ts b/packages/firebase/compat/app/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/app/index.ts rename to packages/firebase/compat/app/index.ts diff --git a/packages/firebase/compat/app/package.json b/packages/firebase/compat/app/package.json new file mode 100644 index 00000000000..b278dbff3cc --- /dev/null +++ b/packages/firebase/compat/app/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/app", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "../index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/auth/index.ts b/packages/firebase/compat/auth/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/auth/index.ts rename to packages/firebase/compat/auth/index.ts diff --git a/packages/firebase/compat/auth/package.json b/packages/firebase/compat/auth/package.json new file mode 100644 index 00000000000..159972bf0d8 --- /dev/null +++ b/packages/firebase/compat/auth/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/auth", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/auth/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/database/index.ts b/packages/firebase/compat/database/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/database/index.ts rename to packages/firebase/compat/database/index.ts diff --git a/packages/firebase/compat/database/package.json b/packages/firebase/compat/database/package.json new file mode 100644 index 00000000000..0416f7df69a --- /dev/null +++ b/packages/firebase/compat/database/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/database", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/database/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/firestore/index.ts b/packages/firebase/compat/firestore/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/firestore/index.ts rename to packages/firebase/compat/firestore/index.ts diff --git a/packages/firebase/compat/firestore/package.json b/packages/firebase/compat/firestore/package.json new file mode 100644 index 00000000000..18e99843db0 --- /dev/null +++ b/packages/firebase/compat/firestore/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/firestore", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/firestore/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/functions/index.ts b/packages/firebase/compat/functions/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/functions/index.ts rename to packages/firebase/compat/functions/index.ts diff --git a/packages/firebase/compat/functions/package.json b/packages/firebase/compat/functions/package.json new file mode 100644 index 00000000000..07db9c7e610 --- /dev/null +++ b/packages/firebase/compat/functions/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/functions", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/functions/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/index.cdn.ts b/packages/firebase/compat/index.cdn.ts similarity index 100% rename from packages-exp/firebase-exp/compat/index.cdn.ts rename to packages/firebase/compat/index.cdn.ts diff --git a/packages/firebase/index.d.ts b/packages/firebase/compat/index.d.ts similarity index 100% rename from packages/firebase/index.d.ts rename to packages/firebase/compat/index.d.ts diff --git a/packages-exp/firebase-exp/compat/index.node.ts b/packages/firebase/compat/index.node.ts similarity index 100% rename from packages-exp/firebase-exp/compat/index.node.ts rename to packages/firebase/compat/index.node.ts diff --git a/packages-exp/firebase-exp/compat/index.perf.ts b/packages/firebase/compat/index.perf.ts similarity index 100% rename from packages-exp/firebase-exp/compat/index.perf.ts rename to packages/firebase/compat/index.perf.ts diff --git a/packages-exp/firebase-exp/compat/index.rn.ts b/packages/firebase/compat/index.rn.ts similarity index 100% rename from packages-exp/firebase-exp/compat/index.rn.ts rename to packages/firebase/compat/index.rn.ts diff --git a/packages-exp/firebase-exp/compat/index.ts b/packages/firebase/compat/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/index.ts rename to packages/firebase/compat/index.ts diff --git a/packages-exp/firebase-exp/compat/messaging/index.ts b/packages/firebase/compat/messaging/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/messaging/index.ts rename to packages/firebase/compat/messaging/index.ts diff --git a/packages/firebase/compat/messaging/package.json b/packages/firebase/compat/messaging/package.json new file mode 100644 index 00000000000..73012e00e38 --- /dev/null +++ b/packages/firebase/compat/messaging/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/messaging", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/messaging/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/package.json b/packages/firebase/compat/package.json similarity index 78% rename from packages-exp/firebase-exp/compat/package.json rename to packages/firebase/compat/package.json index 54576c5d260..506355d8228 100644 --- a/packages-exp/firebase-exp/compat/package.json +++ b/packages/firebase/compat/package.json @@ -1,8 +1,8 @@ { - "name": "firebase-exp/compat", + "name": "firebase/compat", "main": "dist/index.node.cjs", "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", + "module": "dist/index.mjs", "react-native": "dist/index.rn.cjs.js", "typings": "index.d.ts", "components": [ @@ -17,6 +17,5 @@ "firestore", "storage", "database" - ], - "type": "module" + ] } diff --git a/packages-exp/firebase-exp/compat/performance/index.ts b/packages/firebase/compat/performance/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/performance/index.ts rename to packages/firebase/compat/performance/index.ts diff --git a/packages/firebase/compat/performance/package.json b/packages/firebase/compat/performance/package.json new file mode 100644 index 00000000000..257467d57e8 --- /dev/null +++ b/packages/firebase/compat/performance/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/performance", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/performance/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/remote-config/index.ts b/packages/firebase/compat/remote-config/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/remote-config/index.ts rename to packages/firebase/compat/remote-config/index.ts diff --git a/packages/firebase/compat/remote-config/package.json b/packages/firebase/compat/remote-config/package.json new file mode 100644 index 00000000000..e002a4b5470 --- /dev/null +++ b/packages/firebase/compat/remote-config/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/remote-config", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/remote-config/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/rollup.config.js b/packages/firebase/compat/rollup.config.js similarity index 79% rename from packages-exp/firebase-exp/compat/rollup.config.js rename to packages/firebase/compat/rollup.config.js index cd126ac9344..c181cb50f82 100644 --- a/packages-exp/firebase-exp/compat/rollup.config.js +++ b/packages/firebase/compat/rollup.config.js @@ -43,7 +43,8 @@ function createUmdOutputConfig(output) { extend: true, name: GLOBAL_NAME, globals: { - '@firebase/app-compat': GLOBAL_NAME + '@firebase/app-compat': GLOBAL_NAME, + '@firebase/app': `${GLOBAL_NAME}.INTERNAL.modularAPIs` }, /** @@ -53,23 +54,27 @@ function createUmdOutputConfig(output) { * */ intro: ` - try { - (function() {`, + try { + (function() {`, outro: ` - }).apply(this, arguments); - } catch(err) { - console.error(err); - throw new Error( - 'Cannot instantiate ${output} - ' + - 'be sure to load firebase-app.js first.' - ); - }` + }).apply(this, arguments); + } catch(err) { + console.error(err); + throw new Error( + 'Cannot instantiate ${output} - ' + + 'be sure to load firebase-app.js first.' + ); + }` }; } const plugins = [sourcemaps(), resolveModule(), json(), commonjs()]; const typescriptPlugin = rollupTypescriptPlugin({ + typescript +}); + +const typescriptPluginCDN = rollupTypescriptPlugin({ typescript, tsconfigOverride: { compilerOptions: { @@ -97,10 +102,15 @@ const appBuilds = [ file: resolve(__dirname, 'app', appPkg.module), format: 'es', sourcemap: true + }, + { + file: resolve(__dirname, 'app', appPkg.browser), + format: 'es', + sourcemap: true } ], plugins: [...plugins, typescriptPlugin], - external + external: id => external.some(dep => id === dep || id.startsWith(`${dep}/`)), }, /** * App UMD Builds @@ -113,7 +123,7 @@ const appBuilds = [ format: 'umd', name: GLOBAL_NAME }, - plugins: [...plugins, typescriptPlugin, uglify()] + plugins: [...plugins, typescriptPluginCDN, uglify()] } ]; @@ -135,16 +145,21 @@ const componentBuilds = compatPkg.components file: resolve(__dirname, component, pkg.module), format: 'es', sourcemap: true + }, + { + file: resolve(__dirname, component, pkg.browser), + format: 'es', + sourcemap: true } ], plugins: [...plugins, typescriptPlugin], - external + external: id => external.some(dep => id === dep || id.startsWith(`${dep}/`)), }, { input: `${__dirname}/${component}/index.ts`, output: createUmdOutputConfig(`firebase-${component}-compat.js`), - plugins: [...plugins, typescriptPlugin, uglify()], - external: ['@firebase/app-compat'] + plugins: [...plugins, typescriptPluginCDN, uglify()], + external: ['@firebase/app-compat', '@firebase/app'] } ]; }) @@ -167,7 +182,7 @@ const completeBuilds = [ } ], plugins: [...plugins, typescriptPlugin], - external + external: id => external.some(dep => id === dep || id.startsWith(`${dep}/`)), }, { input: `${__dirname}/index.cdn.ts`, @@ -177,7 +192,7 @@ const completeBuilds = [ sourcemap: true, name: GLOBAL_NAME }, - plugins: [...plugins, typescriptPlugin, uglify()] + plugins: [...plugins, typescriptPluginCDN, uglify()] }, /** * App Node.js Builds @@ -190,7 +205,7 @@ const completeBuilds = [ sourcemap: true }, plugins: [...plugins, typescriptPlugin], - external + external: id => external.some(dep => id === dep || id.startsWith(`${dep}/`)), }, /** * App React Native Builds @@ -203,7 +218,7 @@ const completeBuilds = [ sourcemap: true }, plugins: [...plugins, typescriptPlugin], - external + external: id => external.some(dep => id === dep || id.startsWith(`${dep}/`)), }, /** * Performance script Build @@ -219,9 +234,9 @@ const completeBuilds = [ plugins: [ sourcemaps(), resolveModule({ - mainFields: ['lite', 'module', 'main'] + mainFields: ['lite-esm5', 'esm5', 'module'] }), - typescriptPlugin, + typescriptPluginCDN, json(), commonjs(), uglify() diff --git a/packages-exp/firebase-exp/compat/storage/index.ts b/packages/firebase/compat/storage/index.ts similarity index 100% rename from packages-exp/firebase-exp/compat/storage/index.ts rename to packages/firebase/compat/storage/index.ts diff --git a/packages/firebase/compat/storage/package.json b/packages/firebase/compat/storage/package.json new file mode 100644 index 00000000000..4c2fb502e9d --- /dev/null +++ b/packages/firebase/compat/storage/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/compat/storage", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/compat/storage/index.d.ts" +} diff --git a/packages/firebase/database/index.ts b/packages/firebase/database/index.ts index 69306e1c342..912b6daaab2 100644 --- a/packages/firebase/database/index.ts +++ b/packages/firebase/database/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -import '@firebase/database'; +export * from '@firebase/database'; diff --git a/packages/firebase/database/package.json b/packages/firebase/database/package.json index e04ecd0731d..9c332d35149 100644 --- a/packages/firebase/database/package.json +++ b/packages/firebase/database/package.json @@ -1,6 +1,7 @@ { "name": "firebase/database", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/database/index.d.ts" } diff --git a/packages/firebase/empty-import.d.ts b/packages/firebase/empty-import.d.ts deleted file mode 100644 index f9a58fc8b94..00000000000 --- a/packages/firebase/empty-import.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -declare namespace empty {} - -export = empty; diff --git a/packages/firebase/externs/firebase-app-externs.js b/packages/firebase/externs/firebase-app-externs.js deleted file mode 100644 index 06c5db9841e..00000000000 --- a/packages/firebase/externs/firebase-app-externs.js +++ /dev/null @@ -1,293 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase namespace and Firebase App API. - * @externs - */ - -/** - * firebase is a global namespace from which all the Firebase - * services are accessed. - * - * @namespace - */ -var firebase = {}; - -/** - * Creates and initializes a Firebase {@link firebase.app.App app} instance. - * - * See - * {@link - * https://firebase.google.com/docs/web/setup#add_firebase_to_your_app - * Add Firebase to your app} and - * {@link - * https://firebase.google.com/docs/web/setup#initialize_multiple_apps - * Initialize multiple apps} for detailed documentation. - * - * @example - * // Initialize default app - * // Retrieve your own options values by adding a web app on - * // https://console.firebase.google.com - * firebase.initializeApp({ - * apiKey: "AIza....", // Auth / General Use - * authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect - * databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database - * storageBucket: "YOUR_APP.appspot.com", // Storage - * messagingSenderId: "123456789" // Cloud Messaging - * }); - * - * @example - * // Initialize another app - * var otherApp = firebase.initializeApp({ - * databaseURL: "https://.firebaseio.com", - * storageBucket: ".appspot.com" - * }, "otherApp"); - * - * @param {!Object} options Options to configure the app's services. - * @param {string=} name Optional name of the app to initialize. If no name - * is provided, the default is `"[DEFAULT]"`. - * - * @return {!firebase.app.App} The initialized app. - */ -firebase.initializeApp = function (options, name) {}; - -/** - * Retrieves a Firebase {@link firebase.app.App app} instance. - * - * When called with no arguments, the default app is returned. When an app name - * is provided, the app corresponding to that name is returned. - * - * An exception is thrown if the app being retrieved has not yet been - * initialized. - * - * @example - * // Return the default app - * var app = firebase.app(); - * - * @example - * // Return a named app - * var otherApp = firebase.app("otherApp"); - * - * @namespace - * @param {string=} name Optional name of the app to return. If no name is - * provided, the default is `"[DEFAULT]"`. - * - * @return {!firebase.app.App} The app corresponding to the provided app name. - * If no app name is provided, the default app is returned. - */ -firebase.app = function (name) {}; - -/** - * A (read-only) array of all initialized apps. - * @type {!Array} - */ -firebase.apps; - -/** - * The current SDK version. - * @type {string} - */ -firebase.SDK_VERSION; - -/** - * A Firebase App holds the initialization information for a collection of - * services. - * - * Do not call this constructor directly. Instead, use - * {@link firebase#.initializeApp `firebase.initializeApp()`} to create an app. - * - * @interface - */ -firebase.app.App = function () {}; - -/** - * The (read-only) name for this app. - * - * The default app's name is `"[DEFAULT]"`. - * - * @example - * // The default app's name is "[DEFAULT]" - * firebase.initializeApp(defaultAppConfig); - * console.log(firebase.app().name); // "[DEFAULT]" - * - * @example - * // A named app's name is what you provide to initializeApp() - * var otherApp = firebase.initializeApp(otherAppConfig, "other"); - * console.log(otherApp.name); // "other" - * - * @type {string} - */ -firebase.app.App.prototype.name; - -/** - * The (read-only) configuration options for this app. These are the original - * parameters given in - * {@link firebase#.initializeApp `firebase.initializeApp()`}. - * - * @example - * var app = firebase.initializeApp(config); - * console.log(app.options.databaseURL === config.databaseURL); // true - * - * @type {!Object} - */ -firebase.app.App.prototype.options; - -/** - * Renders this app unusable and frees the resources of all associated - * services. - * - * @example - * app.delete() - * .then(function() { - * console.log("App deleted successfully"); - * }) - * .catch(function(error) { - * console.log("Error deleting app:", error); - * }); - * - * @return {!firebase.Promise} An empty promise fulfilled when the app has - * been deleted. - */ -firebase.app.App.prototype.delete = function () {}; - -/** - * A Thenable is the standard interface returned by a Promise. - * - * @template T - * @interface - */ -firebase.Thenable = function () {}; - -/** - * Assign callback functions called when the Thenable value either - * resolves, or is rejected. - * - * @param {(function(T): *)=} onResolve Called when the Thenable resolves. - * @param {(function(!Error): *)=} onReject Called when the Thenable is rejected - * (with an error). - * @return {!firebase.Thenable<*>} - */ -firebase.Thenable.prototype.then = function (onResolve, onReject) {}; - -/** - * Assign a callback when the Thenable rejects. - * - * @param {(function(!Error): *)=} onReject Called when the Thenable is rejected - * (with an error). - * @return {!firebase.Thenable<*>} - */ -firebase.Thenable.prototype.catch = function (onReject) {}; - -/** - * A Promise represents an eventual (asynchronous) value. A Promise should - * (eventually) either resolve or reject. When it does, it will call all the - * callback functions that have been assigned via the .then() or - * .catch() methods. - * - * firebase.Promise is the same as the native Promise - * implementation when available in the current environment, otherwise it is a - * compatible implementation of the Promise/A+ spec. - * - * @template T - * @constructor - * @implements {firebase.Thenable} - * @param {function((function(T): void), - * (function(!Error): void))} resolver - */ -firebase.Promise = function (resolver) {}; - -/** - * Assign callback functions called when the Promise either resolves, or is - * rejected. - * - * @param {(function(T): *)=} onResolve Called when the Promise resolves. - * @param {(function(!Error): *)=} onReject Called when the Promise is rejected - * (with an error). - * @return {!firebase.Promise<*>} - * @override - */ -firebase.Promise.prototype.then = function (onResolve, onReject) {}; - -/** - * Assign a callback when the Promise rejects. - * - * @param {(function(!Error): *)=} onReject Called when the Promise is rejected - * (with an error). - * @override - */ -firebase.Promise.prototype.catch = function (onReject) {}; - -/** - * Return a resolved Promise. - * - * @template T - * @param {T=} value The value to be returned by the Promise. - * @return {!firebase.Promise} - */ -firebase.Promise.resolve = function (value) {}; - -/** - * Return (an immediately) rejected Promise. - * - * @param {!Error} error The reason for the Promise being rejected. - * @return {!firebase.Promise<*>} - */ -firebase.Promise.reject = function (error) {}; - -/** - * Convert an array of Promises, to a single array of values. - * Promise.all() resolves only after all the Promises in the array - * have resolved. - * - * Promise.all() rejects when any of the promises in the Array have - * rejected. - * - * @param {!Array>} values - * @return {!firebase.Promise>} - */ -firebase.Promise.all = function (values) {}; - -/** - * @template V, E - * @interface - **/ -firebase.Observer = function () {}; - -/** - * @param {?V} value - */ -firebase.Observer.prototype.next = function (value) {}; - -/** - * @param {!E} error - */ -firebase.Observer.prototype.error = function (error) {}; - -firebase.Observer.prototype.complete = function () {}; - -/** @typedef {function(): void} */ -firebase.CompleteFn; - -/** @typedef {function(): void} */ -firebase.Unsubscribe; - -/** - * @param {string} name - * @param {string} version - * @param {?string} variant - */ -firebase.registerVersion = function (name, version, variant) {}; diff --git a/packages/firebase/externs/firebase-app-internal-externs.js b/packages/firebase/externs/firebase-app-internal-externs.js deleted file mode 100644 index dd6acd7811b..00000000000 --- a/packages/firebase/externs/firebase-app-internal-externs.js +++ /dev/null @@ -1,183 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase namespace and Firebase App API - INTERNAL methods. - * @externs - */ - -/** - * @param {!Object} component - */ -firebase.INTERNAL.registerComponent = function (component) {}; - -/** @param {!Object} props */ -firebase.INTERNAL.extendNamespace = function (props) {}; - -firebase.INTERNAL.resetNamespace = function () {}; - -/** @typedef {function(*): void} */ -firebase.NextFn; - -/** @typedef {function(!Error): void} */ -firebase.ErrorFn; - -/** - * @typedef {function((firebase.NextFn|firebase.Observer)=, - * firebase.ErrorFn=, - * firebase.CompleteFn=): firebase.Unsubscribe} - */ -firebase.Subscribe; - -/** - * @param {function (!firebase.Observer): void} executor - * @param {(function (!firebase.Observer): void)=} onNoObservers - * @return {!firebase.Subscribe} - */ -firebase.INTERNAL.createSubscribe = function (executor, onNoObservers) {}; - -/** - * @param {*} target - * @param {*} source - */ -firebase.INTERNAL.deepExtend = function (target, source) {}; - -/** @param {string} name */ -firebase.INTERNAL.removeApp = function (name) {}; - -/** - * @type {!Object} - */ -firebase.INTERNAL.factories = {}; - -/** - * @param {!firebase.app.App} app - * @param {string} serviceName - * @return {string|null} - */ -firebase.INTERNAL.useAsService = function (app, serviceName) {}; - -/** - * @constructor - * @param {string} service All lowercase service code (e.g., 'auth') - * @param {string} serviceName Display service name (e.g., 'Auth') - * @param {!Object} errors - */ -firebase.INTERNAL.ErrorFactory = function (service, serviceName, errors) {}; - -/** - * @param {string} code - * @param {Object=} data - * @return {!firebase.FirebaseError} - */ -firebase.INTERNAL.ErrorFactory.prototype.create = function (code, data) {}; - -/** @interface */ -firebase.Service = function () {}; - -/** @type {!firebase.app.App} */ -firebase.Service.prototype.app; - -/** @type {!Object} */ -firebase.Service.prototype.INTERNAL; - -/** @return {firebase.Promise} */ -firebase.Service.prototype.INTERNAL.delete = function () {}; - -/** - * @typedef {function(!firebase.app.App, - * !function(!Object): void, - * string=): !firebase.Service} - */ -firebase.ServiceFactory; - -/** @interface */ -firebase.ServiceNamespace = function () {}; - -/** - * Given an (optional) app, return the instance of the service - * associated with that app. - * - * @param {firebase.app.App=} app - * @return {!firebase.Service} - */ -firebase.ServiceNamespace.prototype.app = function (app) {}; - -/** - * Firebase App.INTERNAL methods - default implementations in firebase-app, - * replaced by Auth ... - */ - -/** - * Listener for an access token. - * - * Should pass null when the user current user is no longer value (signed - * out or credentials become invalid). - * - * Firebear does not currently auto-refresh tokens, BTW - but this interface - * would support that in the future. - * - * @typedef {function(?string): void} - */ -firebase.AuthTokenListener; - -/** - * Returned from app.INTERNAL.getToken(). - * - * @typedef {{ - * accessToken: (string) - * }} - */ -firebase.AuthTokenData; - -/** @type {!Object} */ -firebase.app.App.prototype.INTERNAL; - -/** - * app.INTERNAL.getUid() - * - * @return {?string} - */ -firebase.app.App.prototype.INTERNAL.getUid = function () {}; - -/** - * app.INTERNAL.getToken() - * - * @param {boolean=} forceRefresh Whether to force sts token refresh. - * @return {!Promise} - */ -firebase.app.App.prototype.INTERNAL.getToken = function (forceRefresh) {}; - -/** - * Adds an auth state listener. - * - * @param {!firebase.AuthTokenListener} listener The auth state listener. - */ -firebase.app.App.prototype.INTERNAL.addAuthTokenListener = function ( - listener -) {}; - -/** - * Removes an auth state listener. - * - * @param {!firebase.AuthTokenListener} listener The auth state listener. - */ -firebase.app.App.prototype.INTERNAL.removeAuthTokenListener = function ( - listener -) {}; diff --git a/packages/firebase/externs/firebase-auth-externs.js b/packages/firebase/externs/firebase-auth-externs.js deleted file mode 100644 index f286f66cf64..00000000000 --- a/packages/firebase/externs/firebase-auth-externs.js +++ /dev/null @@ -1,3471 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase Auth API. - * @externs - */ - -/** - * Gets the {@link firebase.auth.Auth `Auth`} service for the default app or a - * given app. - * - * `firebase.auth()` can be called with no arguments to access the default app's - * {@link firebase.auth.Auth `Auth`} service or as `firebase.auth(app)` to - * access the {@link firebase.auth.Auth `Auth`} service associated with a - * specific app. - * - * @example - * // Get the Auth service for the default app - * var defaultAuth = firebase.auth(); - * - * @example - * // Get the Auth service for a given app - * var otherAuth = firebase.auth(otherApp); - * - * @namespace - * @param {!firebase.app.App=} app - * - * @return {!firebase.auth.Auth} - */ -firebase.auth = function (app) {}; - -/** - * Interface that represents the credentials returned by an auth provider. - * Implementations specify the details about each auth provider's credential - * requirements. - * - * @interface - */ -firebase.auth.AuthCredential = function () {}; - -/** - * The authentication provider ID for the credential. - * For example, 'facebook.com', or 'google.com'. - * - * @type {string} - */ -firebase.auth.AuthCredential.prototype.providerId; - -/** - * The authentication sign in method for the credential. - * For example, 'password', or 'emailLink. This corresponds to the sign-in - * method identifier as returned in - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}. - * - * @type {string} - */ -firebase.auth.AuthCredential.prototype.signInMethod; - -/** - * Static method to deserialize a JSON representation of an object into an - * {@link firebase.auth.AuthCredential}. Input can be either Object or the - * stringified representation of the object. When string is provided, - * JSON.parse would be called first. If the JSON input does not represent an - * `AuthCredential`, null is returned. - * @param {string|!Object} json The plain object representation of an - * AuthCredential. - * @return {?firebase.auth.AuthCredential} The auth credential. - */ -firebase.auth.AuthCredential.fromJSON = function (json) {}; - -/** - * Returns a JSON-serializable representation of this object. - * @return {!Object} The plain object representation of the `AuthCredential`. - */ -firebase.auth.AuthCredential.prototype.toJSON = function () {}; - -/** - * Defines the options for initializing an - * {@link firebase.auth.OAuthCredential}. For ID tokens with nonce claim, - * the raw nonce has to also be provided. - * - * @interface - */ -firebase.auth.OAuthCredentialOptions = function () {}; - -/** - * The OAuth ID token used to initialize the OAuthCredential. - * - * @type {string|undefined} - */ -firebase.auth.OAuthCredentialOptions.prototype.idToken; - -/** - * The OAuth access token used to initialize the OAuthCredential. - * - * @type {string|undefined} - */ -firebase.auth.OAuthCredentialOptions.prototype.accessToken; - -/** - * The raw nonce associated with the ID token. It is required when an ID token - * with a nonce field is provided. The SHA-256 hash of the raw nonce must match - * the nonce field in the ID token. - * - * @type {string|undefined} - */ -firebase.auth.OAuthCredentialOptions.prototype.rawNonce; - -/** - * Interface that represents the OAuth credentials returned by an OAuth - * provider. Implementations specify the details about each auth provider's - * credential requirements. - * - * @interface - * @extends {firebase.auth.AuthCredential} - */ -firebase.auth.OAuthCredential = function () {}; - -/** - * The OAuth ID token associated with the credential if it belongs to an - * OIDC provider, such as `google.com`. - * - * @type {?string|undefined} - */ -firebase.auth.OAuthCredential.prototype.idToken; - -/** - * The OAuth access token associated with the credential if it belongs to an - * OAuth provider, such as `facebook.com`, `twitter.com`, etc. - * - * @type {?string|undefined} - */ -firebase.auth.OAuthCredential.prototype.accessToken; - -/** - * The OAuth access token secret associated with the credential if it belongs - * to an OAuth 1.0 provider, such as `twitter.com`. - * - * @type {?string|undefined} - */ -firebase.auth.OAuthCredential.prototype.secret; - -/** - * Interface that represents the phone credentials returned by a phone provider. - * - * @interface - * @extends {firebase.auth.AuthCredential} - */ -firebase.auth.PhoneAuthCredential = function () {}; - -/** - * Gets the {@link firebase.auth.Auth `Auth`} service for the current app. - * - * @example - * var auth = app.auth(); - * // The above is shorthand for: - * // var auth = firebase.auth(app); - * - * @return {!firebase.auth.Auth} - */ -firebase.app.App.prototype.auth = function () {}; - -/** - * Interface representing a user's metadata. - * - * @interface - */ -firebase.auth.UserMetadata = function () {}; - -/** - * The date the user last signed in, formatted as a UTC string. - * For example, 'Fri, 22 Sep 2017 01:49:58 GMT'. - * - * @type {?string} - */ -firebase.auth.UserMetadata.prototype.lastSignInTime; - -/** - * The date the user was created, formatted as a UTC string. - * For example, 'Fri, 22 Sep 2017 01:49:58 GMT'. - * - * @type {?string} - */ -firebase.auth.UserMetadata.prototype.creationTime; - -/** - * User profile information, visible only to the Firebase project's - * apps. - * - * @interface - */ -firebase.UserInfo = function () {}; - -/** - * The user's unique ID. - * - * @type {string} - */ -firebase.UserInfo.prototype.uid; - -/** - * The authentication provider ID for the current user. - * For example, 'facebook.com', or 'google.com'. - * - * @type {string} - */ -firebase.UserInfo.prototype.providerId; - -/** - * The user's email address (if available). - * @type {?string} - */ -firebase.UserInfo.prototype.email; - -/** - * The user's display name (if available). - * - * @type {?string} - */ -firebase.UserInfo.prototype.displayName; - -/** - * The URL of the user's profile picture (if available). - * - * @type {?string} - */ -firebase.UserInfo.prototype.photoURL; - -/** - * The user's E.164 formatted phone number (if available). - * - * @type {?string} - */ -firebase.UserInfo.prototype.phoneNumber; - -/** - * A user account. - * - * @interface - * @extends {firebase.UserInfo} - */ -firebase.User = function () {}; - -/** - * The phone number normalized based on the E.164 standard (e.g. +16505550101) - * for the current user. This is null if the user has no phone credential linked - * to the account. - * @type {?string} - */ -firebase.User.prototype.phoneNumber; - -/** @type {boolean} */ -firebase.User.prototype.isAnonymous; - -/** - * True if the user's email address has been verified. - * @type {boolean} - */ -firebase.User.prototype.emailVerified; - -/** - * Additional metadata about the user. - * @type {!firebase.auth.UserMetadata} - */ -firebase.User.prototype.metadata; - -/** - * Additional provider-specific information about the user. - * @type {!Array} - */ -firebase.User.prototype.providerData; - -/** - * A refresh token for the user account. Use only for advanced scenarios that - * require explicitly refreshing tokens. - * @type {string} - */ -firebase.User.prototype.refreshToken; - -/** - * The {@link firebase.User.MultiFactor} object corresponding to the current - * user. This is used to access all multi-factor properties and operations - * related to the current user. - * @type {!firebase.User.MultiFactorUser} - */ -firebase.User.prototype.multiFactor; - -/** - * The current user's tenant ID. This is a read-only property, which indicates - * the tenant ID used to sign in the current user. This is null if the user is - * signed in from the parent project. - * - * @example - * ```javascript - * // Set the tenant ID on Auth instance. - * firebase.auth().tenantId = ‘TENANT_PROJECT_ID’; - * - * // All future sign-in request now include tenant ID. - * firebase.auth().signInWithEmailAndPassword(email, password) - * .then(function(result) { - * // result.user.tenantId should be ‘TENANT_PROJECT_ID’. - * }).catch(function(error) { - * // Handle error. - * }); - * ``` - * @type {?string} - */ -firebase.User.prototype.tenantId; - -/** - * Returns a JWT token used to identify the user to a Firebase service. - * - * Returns the current token if it has not expired, otherwise this will - * refresh the token and return a new one. - * - * @param {boolean=} forceRefresh Force refresh regardless of token - * expiration. - * @return {!firebase.Promise} - */ -firebase.User.prototype.getIdToken = function (forceRefresh) {}; - -/** - * Refreshes the current user, if signed in. - * - * @return {!firebase.Promise} - */ -firebase.User.prototype.reload = function () {}; - -/** - * Sends a verification email to a user. - * - * The verification process is completed by calling - * {@link firebase.auth.Auth#applyActionCode} - * - *

Error Codes

- *
- *
auth/missing-android-pkg-name
- *
An Android package name must be provided if the Android app is required - * to be installed.
- *
auth/missing-continue-uri
- *
A continue URL must be provided in the request.
- *
auth/missing-ios-bundle-id
- *
An iOS bundle ID must be provided if an App Store ID is provided.
- *
auth/invalid-continue-uri
- *
The continue URL provided in the request is invalid.
- *
auth/unauthorized-continue-uri
- *
The domain of the continue URL is not whitelisted. Whitelist - * the domain in the Firebase console.
- *
- * - * @example - * var actionCodeSettings = { - * url: 'https://www.example.com/cart?email=user@example.com&cartId=123', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * handleCodeInApp: true - * }; - * firebase.auth().currentUser.sendEmailVerification(actionCodeSettings) - * .then(function() { - * // Verification email sent. - * }) - * .catch(function(error) { - * // Error occurred. Inspect error.code. - * }); - * - * @param {?firebase.auth.ActionCodeSettings=} actionCodeSettings The action - * code settings. If specified, the state/continue URL will be set as the - * "continueUrl" parameter in the email verification link. The default email - * verification landing page will use this to display a link to go back to - * the app if it is installed. - * If the actionCodeSettings is not specified, no URL is appended to the - * action URL. - * The state URL provided must belong to a domain that is whitelisted by the - * developer in the console. Otherwise an error will be thrown. - * Mobile app redirects will only be applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of condition. - * The Android package name and iOS bundle ID will be respected only if they - * are configured in the same Firebase Auth project used. - * @return {!firebase.Promise} - */ -firebase.User.prototype.sendEmailVerification = function ( - actionCodeSettings -) {}; - -/** - * Links the user account with the given credentials. - * - *

Error Codes

- *
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/invalid-credential
- *
Thrown if the provider's credential is not valid. This can happen if it - * has already expired when calling link, or if it used invalid token(s). - * See the Firebase documentation for your provider, and make sure you pass - * in the correct parameters to the credential method.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * The fields error.email, error.phoneNumber, and - * error.credential ({@link firebase.auth.AuthCredential}) - * may be provided, depending on the type of credential. You can recover - * from this error by signing in with error.credential directly - * via {@link firebase.auth.Auth#signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User#linkWithCredential} the original credential to that - * newly signed in user.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/invalid-email
- *
Thrown if the email used in a - * {@link firebase.auth.EmailAuthProvider#credential} is invalid.
- *
auth/wrong-password
- *
Thrown if the password used in a - * {@link firebase.auth.EmailAuthProvider#credential} is not correct or - * when the user associated with the email does not have a password.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * ID of the credential is not valid.
- *
- * - * @param {!firebase.auth.AuthCredential} credential The auth credential. - * @return {!firebase.Promise} - */ -firebase.User.prototype.linkWithCredential = function (credential) {}; - -/** - * Links the user account with the given credentials, and returns any available - * additional user information, such as user name. - * - * This method is deprecated. Use - * {@link firebase.User#linkWithCredential} instead. - * - *

Error Codes

- *
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/invalid-credential
- *
Thrown if the provider's credential is not valid. This can happen if it - * has already expired when calling link, or if it used invalid token(s). - * See the Firebase documentation for your provider, and make sure you pass - * in the correct parameters to the credential method.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * The fields error.email, error.phoneNumber, and - * error.credential ({@link firebase.auth.AuthCredential}) - * may be provided, depending on the type of credential. You can recover - * from this error by signing in with error.credential directly - * via {@link firebase.auth.Auth#signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User#linkWithCredential} the original credential to that - * newly signed in user.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/invalid-email
- *
Thrown if the email used in a - * {@link firebase.auth.EmailAuthProvider#credential} is invalid.
- *
auth/wrong-password
- *
Thrown if the password used in a - * {@link firebase.auth.EmailAuthProvider#credential} is not correct or - * when the user associated with the email does not have a password.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * ID of the credential is not valid.
- *
- * - * @param {!firebase.auth.AuthCredential} credential The auth credential. - * @return {!firebase.Promise} - */ -firebase.User.prototype.linkAndRetrieveDataWithCredential = function ( - credential -) {}; - -/** - * Links the user account with the given phone number. - * - *

Error Codes

- *
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/captcha-check-failed
- *
Thrown if the reCAPTCHA response token was invalid, expired, or if - * this method was called from a non-whitelisted domain.
- *
auth/invalid-phone-number
- *
Thrown if the phone number has an invalid format.
- *
auth/missing-phone-number
- *
Thrown if the phone number is missing.
- *
auth/quota-exceeded
- *
Thrown if the SMS quota for the Firebase project has been exceeded.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given phone number has been - * disabled.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the phone number already exists - * among your users, or is already linked to a Firebase User. - * The fields error.phoneNumber and - * error.credential ({@link firebase.auth.AuthCredential}) - * are provided in this case. You can recover from this error by signing in - * with that credential directly via - * {@link firebase.auth.Auth#signInWithCredential}.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the phone authentication provider in the - * Firebase Console. Go to the Firebase Console for your project, in the - * Auth section and the Sign in Method tab and configure - * the provider.
- *
- * - * @param {string} phoneNumber The user's phone number in E.164 format (e.g. - * +16505550101). - * @param {!firebase.auth.ApplicationVerifier} applicationVerifier - * @return {!firebase.Promise} - */ -firebase.User.prototype.linkWithPhoneNumber = function ( - phoneNumber, - applicationVerifier -) {}; - -/** - * Unlinks a provider from a user account. - * - *

Error Codes

- *
- *
auth/no-such-provider
- *
Thrown if the user does not have this provider linked or when the - * provider ID given does not exist.
- * - * - * @param {string} providerId - * @return {!firebase.Promise} - */ -firebase.User.prototype.unlink = function (providerId) {}; - -/** - * Re-authenticates a user using a fresh credential. Use before operations - * such as {@link firebase.User#updatePassword} that require tokens from recent - * sign-in attempts. - * - *

Error Codes

- *
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/user-not-found
- *
Thrown if the credential given does not correspond to any existing user. - *
- *
auth/invalid-credential
- *
Thrown if the provider's credential is not valid. This can happen if it - * has already expired when calling link, or if it used invalid token(s). - * See the Firebase documentation for your provider, and make sure you pass - * in the correct parameters to the credential method.
- *
auth/invalid-email
- *
Thrown if the email used in a - * {@link firebase.auth.EmailAuthProvider#credential} is invalid.
- *
auth/wrong-password
- *
Thrown if the password used in a - * {@link firebase.auth.EmailAuthProvider#credential} is not correct or when - * the user associated with the email does not have a password.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * ID of the credential is not valid.
- *
- * - * @param {!firebase.auth.AuthCredential} credential - * @return {!firebase.Promise} - */ -firebase.User.prototype.reauthenticateWithCredential = function (credential) {}; - -/** - * Re-authenticates a user using a fresh credential, and returns any available - * additional user information, such as user name. Use before operations - * such as {@link firebase.User#updatePassword} that require tokens from recent - * sign-in attempts. - * - * This method is deprecated. Use - * {@link firebase.User#reauthenticateWithCredential} instead. - * - *

Error Codes

- *
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/user-not-found
- *
Thrown if the credential given does not correspond to any existing user. - *
- *
auth/invalid-credential
- *
Thrown if the provider's credential is not valid. This can happen if it - * has already expired when calling link, or if it used invalid token(s). - * See the Firebase documentation for your provider, and make sure you pass - * in the correct parameters to the credential method.
- *
auth/invalid-email
- *
Thrown if the email used in a - * {@link firebase.auth.EmailAuthProvider#credential} is invalid.
- *
auth/wrong-password
- *
Thrown if the password used in a - * {@link firebase.auth.EmailAuthProvider#credential} is not correct or when - * the user associated with the email does not have a password.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * ID of the credential is not valid.
- *
- * - * @param {!firebase.auth.AuthCredential} credential - * @return {!firebase.Promise} - */ -firebase.User.prototype.reauthenticateAndRetrieveDataWithCredential = function ( - credential -) {}; - -/** - * Re-authenticates a user using a fresh credential. Use before operations - * such as {@link firebase.User#updatePassword} that require tokens from recent - * sign-in attempts. - * - *

Error Codes

- *
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/user-not-found
- *
Thrown if the credential given does not correspond to any existing user. - *
- *
auth/captcha-check-failed
- *
Thrown if the reCAPTCHA response token was invalid, expired, or if - * this method was called from a non-whitelisted domain.
- *
auth/invalid-phone-number
- *
Thrown if the phone number has an invalid format.
- *
auth/missing-phone-number
- *
Thrown if the phone number is missing.
- *
auth/quota-exceeded
- *
Thrown if the SMS quota for the Firebase project has been exceeded.
- *
- * - * @param {string} phoneNumber The user's phone number in E.164 format (e.g. - * +16505550101). - * @param {!firebase.auth.ApplicationVerifier} applicationVerifier - * @return {!firebase.Promise} - */ -firebase.User.prototype.reauthenticateWithPhoneNumber = function ( - phoneNumber, - applicationVerifier -) {}; - -/** - * Updates the user's email address. - * - * An email will be sent to the original email address (if it was set) that - * allows to revoke the email address change, in order to protect them from - * account hijacking. - * - * Important: this is a security sensitive operation that requires the - * user to have recently signed in. If this requirement isn't met, ask the user - * to authenticate again and then call - * {@link firebase.User#reauthenticateWithCredential}. - * - *

Error Codes

- *
- *
auth/invalid-email
- *
Thrown if the email used is invalid.
- *
auth/email-already-in-use
- *
Thrown if the email is already used by another user.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User#reauthenticateWithCredential} to - * resolve. This does not apply if the user is anonymous.
- *
- * - * @param {string} newEmail The new email address. - * @return {!firebase.Promise} - */ -firebase.User.prototype.updateEmail = function (newEmail) {}; - -/** - * Updates the user's password. - * - * Important: this is a security sensitive operation that requires the - * user to have recently signed in. If this requirement isn't met, ask the user - * to authenticate again and then call - * {@link firebase.User#reauthenticateWithCredential}. - * - *

Error Codes

- *
- *
auth/weak-password
- *
Thrown if the password is not strong enough.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User#reauthenticateWithCredential} to - * resolve. This does not apply if the user is anonymous.
- *
- * - * @param {string} newPassword - * @return {!firebase.Promise} - */ -firebase.User.prototype.updatePassword = function (newPassword) {}; - -/** - * Updates the user's phone number. - * - *

Error Codes

- *
- *
auth/invalid-verification-code
- *
Thrown if the verification code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the verification ID of the credential is not valid.
- *
- * - * @param {!firebase.auth.AuthCredential} phoneCredential - * @return {!firebase.Promise} - */ -firebase.User.prototype.updatePhoneNumber = function (phoneCredential) {}; - -/** - * Updates a user's profile data. - * - * @example - * // Updates the user attributes: - * user.updateProfile({ - * displayName: "Jane Q. User", - * photoURL: "https://example.com/jane-q-user/profile.jpg" - * }).then(function() { - * // Profile updated successfully! - * // "Jane Q. User" - * var displayName = user.displayName; - * // "https://example.com/jane-q-user/profile.jpg" - * var photoURL = user.photoURL; - * }, function(error) { - * // An error happened. - * }); - * - * // Passing a null value will delete the current attribute's value, but not - * // passing a property won't change the current attribute's value: - * // Let's say we're using the same user than before, after the update. - * user.updateProfile({photoURL: null}).then(function() { - * // Profile updated successfully! - * // "Jane Q. User", hasn't changed. - * var displayName = user.displayName; - * // Now, this is null. - * var photoURL = user.photoURL; - * }, function(error) { - * // An error happened. - * }); - * - * @param {!{displayName: ?string, photoURL: ?string}} profile The profile's - * displayName and photoURL to update. - * @return {!firebase.Promise} - */ -firebase.User.prototype.updateProfile = function (profile) {}; - -/** - * Sends a verification email to a new email address. The user's email will be - * updated to the new one after being verified. - * - * If you have a custom email action handler, you can complete the verification - * process by calling {@link firebase.auth.Auth.applyActionCode}. - * - *

Error Codes

- *
- *
auth/missing-android-pkg-name
- *
An Android package name must be provided if the Android app is required - * to be installed.
- *
auth/missing-continue-uri
- *
A continue URL must be provided in the request.
- *
auth/missing-ios-bundle-id
- *
An iOS bundle ID must be provided if an App Store ID is provided.
- *
auth/invalid-continue-uri
- *
The continue URL provided in the request is invalid.
- *
auth/unauthorized-continue-uri
- *
The domain of the continue URL is not whitelisted. Whitelist - * the domain in the Firebase console.
- *
- * - * @example - * ```javascript - * var actionCodeSettings = { - * url: 'https://www.example.com/cart?email=user@example.com&cartId=123', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * handleCodeInApp: true - * }; - * firebase.auth().currentUser.verifyBeforeUpdateEmail( - * 'user@example.com', actionCodeSettings) - * .then(function() { - * // Verification email sent. - * }) - * .catch(function(error) { - * // Error occurred. Inspect error.code. - * }); - * ``` - * - * @param {string} newEmail The email address to be verified and updated to. - * @param {?firebase.auth.ActionCodeSettings=} actionCodeSettings The action - * code settings. If specified, the state/continue URL will be set as the - * "continueUrl" parameter in the email verification link. The default email - * verification landing page will use this to display a link to go back to - * the app if it is installed. - * If the actionCodeSettings is not specified, no URL is appended to the - * action URL. - * The state URL provided must belong to a domain that is whitelisted by the - * developer in the console. Otherwise an error will be thrown. - * Mobile app redirects will only be applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of condition. - * The Android package name and iOS bundle ID will be respected only if they - * are configured in the same Firebase Auth project used. - * @return {!firebase.Promise} - */ -firebase.User.prototype.verifyBeforeUpdateEmail = function ( - newEmail, - actionCodeSettings -) {}; - -/** - * Deletes and signs out the user. - * - * Important: this is a security sensitive operation that requires the - * user to have recently signed in. If this requirement isn't met, ask the user - * to authenticate again and then call - * {@link firebase.User#reauthenticateWithCredential}. - * - *

Error Codes

- *
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User#reauthenticateWithCredential} to - * resolve. This does not apply if the user is anonymous.
- *
- * - * @return {!firebase.Promise} - */ -firebase.User.prototype.delete = function () {}; - -/** - * Returns a JSON-serializable representation of this object. - * - * @return {!Object} A JSON-serializable representation of this object. - */ -firebase.User.prototype.toJSON = function () {}; - -/** - * The Firebase Auth service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.auth `firebase.auth()`}. - * - * See - * {@link https://firebase.google.com/docs/auth/ Firebase Authentication} - * for a full guide on how to use the Firebase Auth service. - * - * @interface - */ -firebase.auth.Auth = function () {}; - -/** - * Checks a password reset code sent to the user by email or other out-of-band - * mechanism. - * - * Returns the user's email address if valid. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if the password reset code has expired.
- *
auth/invalid-action-code
- *
Thrown if the password reset code is invalid. This can happen if the code - * is malformed or has already been used.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given password reset code has - * been disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the password reset code. This - * may have happened if the user was deleted between when the code was - * issued and when this method was called.
- *
- * - * @param {string} code A verification code sent to the user. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.verifyPasswordResetCode = function (code) {}; - -/** - * A response from {@link firebase.auth.Auth#checkActionCode}. - * - * @interface - */ -firebase.auth.ActionCodeInfo = function () {}; - -/** - * The data associated with the action code. - * - * For the `PASSWORD_RESET`, `VERIFY_EMAIL`, and `RECOVER_EMAIL` actions, this - * object contains an `email` field with the address the email was sent to. - * - * For the RECOVER_EMAIL action, which allows a user to undo an email address - * change, this object also contains a `previousEmail` field with the user - * account's current email address. After the action completes, the user's - * email address will revert to the value in the `email` field from the value - * in `previousEmail` field. - * - * For the VERIFY_AND_CHANGE_EMAIL action, which allows a user to verify the - * email before updating it, this object contains a `previousEmail` field with - * the user account's email address before updating. After the action completes, - * the user's email address will be updated to the value in the `email` field - * from the value in `previousEmail` field. - * - * For the REVERT_SECOND_FACTOR_ADDITION action, which allows a user to unenroll - * a newly added second factor, this object contains a `multiFactorInfo` field - * with the information about the second factor. For phone second factor, the - * `multiFactorInfo` is a {@link firebase.auth.Auth.PhoneMultiFactorInfo} - * object, which contains the phone number. - * - * @typedef {{ - * email: (?string|undefined), - * fromEmail: (?string|undefined), - * multiFactorInfo: (?firebase.auth.MultiFactorInfo|undefined), - previousEmail: (?string|undefined) - * }} - */ -firebase.auth.ActionCodeInfo.prototype.data; - -/** - * The type of operation that generated the action code. This could be: - *
    - *
  • `EMAIL_SIGNIN`: email sign in code generated via - * {@link firebase.auth.Auth.sendSignInLinkToEmail}.
  • - *
  • `PASSWORD_RESET`: password reset code generated via - * {@link firebase.auth.Auth.sendPasswordResetEmail}.
  • - *
  • `RECOVER_EMAIL`: email change revocation code generated via - * {@link firebase.User.updateEmail}.
  • - *
  • `REVERT_SECOND_FACTOR_ADDITION`: revert second factor addition - * code generated via - * {@link firebase.User.MultiFactorUser.enroll}.
  • - *
  • `VERIFY_AND_CHANGE_EMAIL`: verify and change email code generated - * via {@link firebase.User.verifyBeforeUpdateEmail}.
  • - *
  • `VERIFY_EMAIL`: email verification code generated via - * {@link firebase.User.sendEmailVerification}.
  • - *
- * - * @type {string} - */ -firebase.auth.ActionCodeInfo.prototype.operation; - -/** - * @enum {string} - * An enumeration of the possible email action types. - */ -firebase.auth.ActionCodeInfo.Operation = { - /** - * The email link sign in email action. - */ - EMAIL_SIGNIN: 'EMAIL_SIGNIN', - /** - * The reset password email action. - */ - PASSWORD_RESET: 'PASSWORD_RESET', - /** - * The email revocation action. - */ - RECOVER_EMAIL: 'RECOVER_EMAIL', - /** - * The revert second factor addition email action. - */ - REVERT_SECOND_FACTOR_ADDITION: 'REVERT_SECOND_FACTOR_ADDITION', - /** - * The verify and update email action. - */ - VERIFY_AND_CHANGE_EMAIL: 'VERIFY_AND_CHANGE_EMAIL', - /** - * The email verification action. - */ - VERIFY_EMAIL: 'VERIFY_EMAIL' -}; - -/** - * A utility class to parse email action URLs. - * - * @constructor - */ -firebase.auth.ActionCodeURL = function () {}; - -/** - * The API key of the email action link. - * - * @type {string} - */ -firebase.auth.ActionCodeURL.prototype.apiKey; - -/** - * The action code of the email action link. - * - * @type {string} - */ -firebase.auth.ActionCodeURL.prototype.code; - -/** - * The continue URL of the email action link. Null if not provided. - * - * @type {?string} - */ -firebase.auth.ActionCodeURL.prototype.continueUrl; - -/** - * The language code of the email action link. Null if not provided. - * - * @type {?string} - */ -firebase.auth.ActionCodeURL.prototype.languageCode; - -/** - * The action performed by the email action link. It returns from one - * of the types from {@link firebase.auth.ActionCodeInfo}. - * - * @type {!firebase.auth.ActionCodeInfo.Operation} - */ -firebase.auth.ActionCodeURL.prototype.operation; - -/** - * The tenant ID of the email action link. Null if the email action - * is from the parent project. - * - * @type {?string} - */ -firebase.auth.ActionCodeURL.prototype.tenantId; - -/** - * Parses the email action link string and returns an ActionCodeURL object - * if the link is valid, otherwise returns null. - * - * @param {string} link The email action link string. - * @return {?firebase.auth.ActionCodeURL} The ActionCodeURL object, or null if - * the link is invalid. - */ -firebase.auth.ActionCodeURL.parseLink = function (link) {}; - -/** - * This is the interface that defines the required continue/state URL with - * optional Android and iOS bundle identifiers. - * The action code setting fields are: - *
    - *
  • url: Sets the link continue/state URL, which has different meanings - * in different contexts:

    - *
      - *
    • When the link is handled in the web action widgets, this is the deep - * link in the continueUrl query parameter.
    • - *
    • When the link is handled in the app directly, this is the continueUrl - * query parameter in the deep link of the Dynamic Link.
    • - *
    - *
  • - *
  • iOS: Sets the iOS bundle ID. This will try to open the link in an iOS app - * if it is installed.
  • - *
  • android: Sets the Android package name. This will try to open the link in - * an android app if it is installed. If installApp is passed, it specifies - * whether to install the Android app if the device supports it and the app - * is not already installed. If this field is provided without a - * packageName, an error is thrown explaining that the packageName must be - * provided in conjunction with this field. - * If minimumVersion is specified, and an older version of the app is - * installed, the user is taken to the Play Store to upgrade the app.
  • - *
  • handleCodeInApp: The default is false. When set to true, the action code - * link will be be sent as a Universal Link or Android App Link and will be - * opened by the app if installed. In the false case, the code will be sent - * to the web widget first and then on continue will redirect to the app if - * installed.
  • - *
- * - * @typedef {{ - * url: string, - * iOS: ({bundleId: string}|undefined), - * android: ({packageName: string, installApp: (boolean|undefined), - * minimumVersion: (string|undefined)}|undefined), - * handleCodeInApp: (boolean|undefined) - * }} - */ -firebase.auth.ActionCodeSettings; - -/** - * Checks a verification code sent to the user by email or other out-of-band - * mechanism. - * - * Returns metadata about the code. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if the action code has expired.
- *
auth/invalid-action-code
- *
Thrown if the action code is invalid. This can happen if the code is - * malformed or has already been used.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given action code has been - * disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the action code. This may - * have happened if the user was deleted between when the action code was - * issued and when this method was called.
- *
- * - * @param {string} code A verification code sent to the user. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.checkActionCode = function (code) {}; - -/** - * Applies a verification code sent to the user by email or other out-of-band - * mechanism. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if the action code has expired.
- *
auth/invalid-action-code
- *
Thrown if the action code is invalid. This can happen if the code is - * malformed or has already been used.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given action code has been - * disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the action code. This may - * have happened if the user was deleted between when the action code was - * issued and when this method was called.
- *
- * - * @param {string} code A verification code sent to the user. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.applyActionCode = function (code) {}; - -/** - * The {@link firebase.app.App app} associated with the `Auth` service - * instance. - * - * @example - * var app = auth.app; - * - * @type {!firebase.app.App} - */ -firebase.auth.Auth.prototype.app; - -/** - * The currently signed-in user (or null). - * - * @type {firebase.User|null} - */ -firebase.auth.Auth.prototype.currentUser; - -/** - * The full emulator configuration as set on `auth().emulatorConfig`. - *
    - *
  • protocol: the protocol used by the emulator (http or https).
  • - *
  • host: the host used to reach the emulator.
  • - *
  • port: the port used to reach the emulator.
  • - *
  • options: a list of options used to configure the SDK's interaction with - * the emulator.
  • - *
- * - * @typedef {{ - * protocol: string, - * host: string, - * port: (number|null), - * options: { - * disableWarnings: boolean, - * } - * }} - */ -firebase.auth.EmulatorConfig; - -/** - * The current emulator configuration, or null if not set. - * - * @type {firebase.auth.EmulatorConfig|null} - */ -firebase.auth.Auth.prototype.emulatorConfig; - -/** - * Configures the SDK to communicate with the Firebase Auth emulator. - * - * This must be called before any other Auth SDK actions are taken. - * - * Options can include `disableWarnings`. When set to true, the SDK will not - * display a warning banner at the bottom of the page. - * - * @param {string} url The full emulator url including scheme and port. - * @param {!Object=} options Options for configuring the SDK's emulator config. - */ -firebase.auth.Auth.prototype.useEmulator = function (url, options) {}; - -/** - * The current Auth instance's tenant ID. This is a readable/writable - * property. When you set the tenant ID of an Auth instance, all future - * sign-in/sign-up operations will pass this tenant ID and sign in or - * sign up users to the specified tenant project. - * When set to null, users are signed in to the parent project. By default, - * this is set to null. - * - * @example - * ```javascript - * // Set the tenant ID on Auth instance. - * firebase.auth().tenantId = ‘TENANT_PROJECT_ID’; - * - * // All future sign-in request now include tenant ID. - * firebase.auth().signInWithEmailAndPassword(email, password) - * .then(function(result) { - * // result.user.tenantId should be ‘TENANT_PROJECT_ID’. - * }).catch(function(error) { - * // Handle error. - * }); - * ``` - * - * @type {?string} - */ -firebase.auth.Auth.prototype.tenantId; - -/** - * @enum {string} - * An enumeration of the possible persistence mechanism types. - */ -firebase.auth.Auth.Persistence = { - /** - * Indicates that the state will be persisted even when the browser window is - * closed or the activity is destroyed in react-native. - */ - LOCAL: 'local', - /** - * Indicates that the state will only be stored in memory and will be cleared - * when the window or activity is refreshed. - */ - NONE: 'none', - /** - * Indicates that the state will only persist in current session/tab, relevant - * to web only, and will be cleared when the tab is closed. - */ - SESSION: 'session' -}; - -/** - * Changes the current type of persistence on the current Auth instance for the - * currently saved Auth session and applies this type of persistence for - * future sign-in requests, including sign-in with redirect requests. This will - * return a promise that will resolve once the state finishes copying from one - * type of storage to the other. - * Calling a sign-in method after changing persistence will wait for that - * persistence change to complete before applying it on the new Auth state. - * - * This makes it easy for a user signing in to specify whether their session - * should be remembered or not. It also makes it easier to never persist the - * Auth state for applications that are shared by other users or have sensitive - * data. - * - * The default for web browser apps and React Native apps is 'local' (provided - * the browser supports this mechanism) whereas it is 'none' for Node.js backend - * apps. - * - *

Error Codes (thrown synchronously)

- *
- *
auth/invalid-persistence-type
- *
Thrown if the specified persistence type is invalid.
- *
auth/unsupported-persistence-type
- *
Thrown if the current environment does not support the specified - * persistence type.
- *
- * - * @example - * firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION) - * .then(function() { - * // Existing and future Auth states are now persisted in the current - * // session only. Closing the window would clear any existing state even if - * // a user forgets to sign out. - * }); - * - * @param {!firebase.auth.Auth.Persistence} persistence The auth state - * persistence mechanism. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.setPersistence = function (persistence) {}; - -/** - * The current Auth instance's language code. This is a readable/writable - * property. When set to null, the default Firebase Console language setting - * is applied. The language code will propagate to email action templates - * (password reset, email verification and email change revocation), SMS - * templates for phone authentication, reCAPTCHA verifier and OAuth - * popup/redirect operations provided the specified providers support - * localization with the language code specified. - * - * @type {?string} - */ -firebase.auth.Auth.prototype.languageCode; - -/** - * Sets the current language to the default device/browser preference. - */ -firebase.auth.Auth.prototype.useDeviceLanguage = function () {}; - -/** - * The current Auth instance's settings. This is used to edit/read configuration - * related options like app verification mode for phone authentication. - * - * @type {!firebase.auth.AuthSettings} - */ -firebase.auth.Auth.prototype.settings; - -/** - * Creates a new user account associated with the specified email address and - * password. - * - * On successful creation of the user account, this user will also be - * signed in to your application. - * - * User account creation can fail if the account already exists or the password - * is invalid. - * - * Note: The email address acts as a unique identifier for the user and - * enables an email-based password reset. This function will create - * a new user account and set the initial user password. - * - *

Error Codes

- *
- *
auth/email-already-in-use
- *
Thrown if there already exists an account with the given email - * address.
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/operation-not-allowed
- *
Thrown if email/password accounts are not enabled. Enable email/password - * accounts in the Firebase Console, under the Auth tab.
- *
auth/weak-password
- *
Thrown if the password is not strong enough.
- *
- * - * @example - * firebase.auth().createUserWithEmailAndPassword(email, password) - * .catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * if (errorCode == 'auth/weak-password') { - * alert('The password is too weak.'); - * } else { - * alert(errorMessage); - * } - * console.log(error); - * }); - * - * @param {string} email The user's email address. - * @param {string} password The user's chosen password. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.createUserWithEmailAndPassword = function ( - email, - password -) {}; - -/** - * Gets the list of possible sign in methods for the given email address. This - * is useful to differentiate methods of sign-in for the same provider, - * eg. `EmailAuthProvider` which has 2 methods of sign-in, email/password and - * email/link. - * - *

Error Codes

- *
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
- * - * @param {string} email An email address. - * @return {!firebase.Promise>} - */ -firebase.auth.Auth.prototype.fetchSignInMethodsForEmail = function (email) {}; - -/** - * Checks if an incoming link is a sign-in with email link. - * - * @param {string} emailLink Sign-in email link. - * @return {boolean} Whether the link is a sign-in with email link. - */ -firebase.auth.Auth.prototype.isSignInWithEmailLink = function (emailLink) {}; - -/** - * Adds an observer for changes to the user's sign-in state. - * - * Prior to 4.0.0, this triggered the observer when users were signed in, - * signed out, or when the user's ID token changed in situations such as token - * expiry or password change. After 4.0.0, the observer is only triggered - * on sign-in or sign-out. - * - * To keep the old behavior, see {@link firebase.auth.Auth#onIdTokenChanged}. - * - * @example - * firebase.auth().onAuthStateChanged(function(user) { - * if (user) { - * // User is signed in. - * } - * }); - * - * @param {!firebase.Observer|function(?firebase.User)} - * nextOrObserver An observer object or a function triggered on change. - * @param {function(!firebase.auth.Error)=} error Optional A function - * triggered on auth error. - * @param {firebase.CompleteFn=} completed Optional A function triggered when the - * observer is removed. - * @return {!firebase.Unsubscribe} The unsubscribe function for the observer. - */ -firebase.auth.Auth.prototype.onAuthStateChanged = function ( - nextOrObserver, - error, - completed -) {}; - -/** - * Adds an observer for changes to the signed-in user's ID token, which includes - * sign-in, sign-out, and token refresh events. This method has the same - * behavior as {@link firebase.auth.Auth#onAuthStateChanged} had prior to 4.0.0. - * - * @example - * firebase.auth().onIdTokenChanged(function(user) { - * if (user) { - * // User is signed in or token was refreshed. - * } - * }); - * - * @param {!firebase.Observer|function(?firebase.User)} - * nextOrObserver An observer object or a function triggered on change. - * @param {function(!firebase.auth.Error)=} error Optional A function - * triggered on auth error. - * @param {firebase.CompleteFn=} completed Optional A function triggered when the - * observer is removed. - * @return {!firebase.Unsubscribe} The unsubscribe function for the observer. - */ -firebase.auth.Auth.prototype.onIdTokenChanged = function ( - nextOrObserver, - error, - completed -) {}; - -/** - * Sends a sign-in email link to the user with the specified email. - * - * The sign-in operation has to always be completed in the app unlike other out - * of band email actions (password reset and email verifications). This is - * because, at the end of the flow, the user is expected to be signed in and - * their Auth state persisted within the app. - * - * To complete sign in with the email link, call - * {@link firebase.auth.Auth#signInWithEmailLink} with the email address and - * the email link supplied in the email sent to the user. - * - *

Error Codes

- *
- *
auth/argument-error
- *
Thrown if handleCodeInApp is false.
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/missing-android-pkg-name
- *
An Android package name must be provided if the Android app is required - * to be installed.
- *
auth/missing-continue-uri
- *
A continue URL must be provided in the request.
- *
auth/missing-ios-bundle-id
- *
An iOS Bundle ID must be provided if an App Store ID is provided.
- *
auth/invalid-continue-uri
- *
The continue URL provided in the request is invalid.
- *
auth/unauthorized-continue-uri
- *
The domain of the continue URL is not whitelisted. Whitelist - * the domain in the Firebase console.
- *
- * - * @example - * var actionCodeSettings = { - * // The URL to redirect to for sign-in completion. This is also the deep - * // link for mobile redirects. The domain (www.example.com) for this URL - * // must be whitelisted in the Firebase Console. - * url: 'https://www.example.com/finishSignUp?cartId=1234', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * // This must be true. - * handleCodeInApp: true - * }; - * firebase.auth().sendSignInLinkToEmail('user@example.com', actionCodeSettings) - * .then(function() { - * // The link was successfully sent. Inform the user. Save the email - * // locally so you don't need to ask the user for it again if they open - * // the link on the same device. - * }) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * }); - * - * @param {string} email The email account to sign in with. - * @param {!firebase.auth.ActionCodeSettings} actionCodeSettings The action - * code settings. The action code settings which provides Firebase with - * instructions on how to construct the email link. This includes the - * sign in completion URL or the deep link for mobile redirects, the mobile - * apps to use when the sign-in link is opened on an Android or iOS device. - * Mobile app redirects will only be applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of condition. - * The Android package name and iOS bundle ID will be respected only if they - * are configured in the same Firebase Auth project used. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.sendSignInLinkToEmail = function ( - email, - actionCodeSettings -) {}; - -/** - * Sends a password reset email to the given email address. - * - * To complete the password reset, call - * {@link firebase.auth.Auth#confirmPasswordReset} with the code supplied in the - * email sent to the user, along with the new password specified by the user. - * - *

Error Codes

- *
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/missing-android-pkg-name
- *
An Android package name must be provided if the Android app is required - * to be installed.
- *
auth/missing-continue-uri
- *
A continue URL must be provided in the request.
- *
auth/missing-ios-bundle-id
- *
An iOS Bundle ID must be provided if an App Store ID is provided.
- *
auth/invalid-continue-uri
- *
The continue URL provided in the request is invalid.
- *
auth/unauthorized-continue-uri
- *
The domain of the continue URL is not whitelisted. Whitelist - * the domain in the Firebase console.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the email address.
- *
- * - * @example - * var actionCodeSettings = { - * url: 'https://www.example.com/?email=user@example.com', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * handleCodeInApp: true - * }; - * firebase.auth().sendPasswordResetEmail( - * 'user@example.com', actionCodeSettings) - * .then(function() { - * // Password reset email sent. - * }) - * .catch(function(error) { - * // Error occurred. Inspect error.code. - * }); - * - * @param {string} email The email address with the password to be reset. - * @param {?firebase.auth.ActionCodeSettings=} actionCodeSettings The action - * code settings. If specified, the state/continue URL will be set as the - * "continueUrl" parameter in the password reset link. The default password - * reset landing page will use this to display a link to go back to the app - * if it is installed. - * If the actionCodeSettings is not specified, no URL is appended to the - * action URL. - * The state URL provided must belong to a domain that is whitelisted by the - * developer in the console. Otherwise an error will be thrown. - * Mobile app redirects will only be applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of condition. - * The Android package name and iOS bundle ID will be respected only if they - * are configured in the same Firebase Auth project used. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.sendPasswordResetEmail = function ( - email, - actionCodeSettings -) {}; - -/** - * Completes the password reset process, given a confirmation code and new - * password. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if the password reset code has expired.
- *
auth/invalid-action-code
- *
Thrown if the password reset code is invalid. This can happen if the - * code is malformed or has already been used.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given password reset code has - * been disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the password reset code. This - * may have happened if the user was deleted between when the code was - * issued and when this method was called.
- *
auth/weak-password
- *
Thrown if the new password is not strong enough.
- *
- * - * @param {string} code The confirmation code send via email to the user. - * @param {string} newPassword The new password. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.confirmPasswordReset = function ( - code, - newPassword -) {}; - -/** - * Asynchronously signs in with the given credentials. - * - *

Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail} and then asking the - * user to sign in using one of the returned providers. Once the user is - * signed in, the original credential can be linked to the user with - * {@link firebase.User#linkWithCredential}.
- *
auth/invalid-credential
- *
Thrown if the credential is malformed or has expired.
- *
auth/operation-not-allowed
- *
Thrown if the type of account corresponding to the credential - * is not enabled. Enable the account type in the Firebase Console, under - * the Auth tab.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given credential has been - * disabled.
- *
auth/user-not-found
- *
Thrown if signing in with a credential from - * {@link firebase.auth.EmailAuthProvider#credential} and there is no user - * corresponding to the given email.
- *
auth/wrong-password
- *
Thrown if signing in with a credential from - * {@link firebase.auth.EmailAuthProvider#credential} and the password is - * invalid for the given email, or if the account corresponding to the email - * does not have a password set.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * ID of the credential is not valid.
- *
- * - * @example - * firebase.auth().signInWithCredential(credential).catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * // The email of the user's account used. - * var email = error.email; - * // The firebase.auth.AuthCredential type that was used. - * var credential = error.credential; - * if (errorCode === 'auth/account-exists-with-different-credential') { - * alert('Email already associated with another account.'); - * // Handle account linking here, if using. - * } else { - * console.error(error); - * } - * }); - * - * @param {!firebase.auth.AuthCredential} credential The auth credential. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInWithCredential = function (credential) {}; - -/** - * Asynchronously signs in with the given credentials, and returns any available - * additional user information, such as user name. - * - * This method is deprecated. Use - * {@link firebase.auth.Auth#signInWithCredential} instead. - * - *

Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail} and then asking the - * user to sign in using one of the returned providers. Once the user is - * signed in, the original credential can be linked to the user with - * {@link firebase.User#linkWithCredential}.
- *
auth/invalid-credential
- *
Thrown if the credential is malformed or has expired.
- *
auth/operation-not-allowed
- *
Thrown if the type of account corresponding to the credential - * is not enabled. Enable the account type in the Firebase Console, under - * the Auth tab.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given credential has been - * disabled.
- *
auth/user-not-found
- *
Thrown if signing in with a credential from - * {@link firebase.auth.EmailAuthProvider#credential} and there is no user - * corresponding to the given email.
- *
auth/wrong-password
- *
Thrown if signing in with a credential from - * {@link firebase.auth.EmailAuthProvider#credential} and the password is - * invalid for the given email, or if the account corresponding to the email - * does not have a password set.
- *
auth/invalid-verification-code
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * code of the credential is not valid.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider#credential} and the verification - * ID of the credential is not valid.
- *
- * - * @example - * firebase.auth().signInAndRetrieveDataWithCredential(credential) - * .then(function(userCredential) { - * console.log(userCredential.additionalUserInfo.username); - * }); - * - * @param {!firebase.auth.AuthCredential} credential The auth credential. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInAndRetrieveDataWithCredential = function ( - credential -) {}; - -/** - * Asynchronously signs in using a custom token. - * - * Custom tokens are used to integrate Firebase Auth with existing auth systems, - * and must be generated by the auth backend. - * - * Fails with an error if the token is invalid, expired, or not accepted by the - * Firebase Auth service. - * - *

Error Codes

- *
- *
auth/custom-token-mismatch
- *
Thrown if the custom token is for a different Firebase App.
- *
auth/invalid-custom-token
- *
Thrown if the custom token format is incorrect.
- *
- * - * @example - * firebase.auth().signInWithCustomToken(token).catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * if (errorCode === 'auth/invalid-custom-token') { - * alert('The token you provided is not valid.'); - * } else { - * console.error(error); - * } - * }); - * - * @param {string} token The custom token to sign in with. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInWithCustomToken = function (token) {}; - -/** - * Asynchronously signs in using an email and password. - * - * Fails with an error if the email address and password do not match. - * - * Note: The user's password is NOT the password used to access the user's email - * account. The email address serves as a unique identifier for the user, and - * the password is used to access the user's account in your Firebase project. - * - * See also: {@link firebase.auth.Auth#createUserWithEmailAndPassword}. - * - *

Error Codes

- *
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given email has been - * disabled.
- *
auth/user-not-found
- *
Thrown if there is no user corresponding to the given email.
- *
auth/wrong-password
- *
Thrown if the password is invalid for the given email, or the account - * corresponding to the email does not have a password set.
- *
- * - * @example - * firebase.auth().signInWithEmailAndPassword(email, password) - * .catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * if (errorCode === 'auth/wrong-password') { - * alert('Wrong password.'); - * } else { - * alert(errorMessage); - * } - * console.log(error); - * }); - * - * @param {string} email The users email address. - * @param {string} password The users password. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInWithEmailAndPassword = function ( - email, - password -) {}; - -/** - * Asynchronously signs in using an email and sign-in email link. If no link - * is passed, the link is inferred from the current URL. - * - * Fails with an error if the email address is invalid or OTP in email link - * expires. - * - * Note: Confirm the link is a sign-in email link before calling this method - * {@link firebase.auth.Auth#isSignInWithEmailLink}. - * - *

Error Codes

- *
- *
auth/expired-action-code
- *
Thrown if OTP in email link expires.
- *
auth/invalid-email
- *
Thrown if the email address is not valid.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given email has been - * disabled.
- *
- * - * @example - * firebase.auth().signInWithEmailLink(email, emailLink) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * // Common errors could be invalid email and invalid or expired OTPs. - * }); - * - * @param {string} email The email account to sign in with. - * @param {?string=} emailLink The optional link which contains the OTP needed - * to complete the sign in with email link. If not specified, the current - * URL is used instead. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInWithEmailLink = function ( - email, - emailLink -) {}; - -/** - * Asynchronously signs in using a phone number. This method sends a code via - * SMS to the given phone number, and returns a - * {@link firebase.auth.ConfirmationResult}. After the user provides the code - * sent to their phone, call {@link firebase.auth.ConfirmationResult#confirm} - * with the code to sign the user in. - * - * For abuse prevention, this method also requires a - * {@link firebase.auth.ApplicationVerifier}. The Firebase Auth SDK includes - * a reCAPTCHA-based implementation, {@link firebase.auth.RecaptchaVerifier}. - * - *

Error Codes

- *
- *
auth/captcha-check-failed
- *
Thrown if the reCAPTCHA response token was invalid, expired, or if - * this method was called from a non-whitelisted domain.
- *
auth/invalid-phone-number
- *
Thrown if the phone number has an invalid format.
- *
auth/missing-phone-number
- *
Thrown if the phone number is missing.
- *
auth/quota-exceeded
- *
Thrown if the SMS quota for the Firebase project has been exceeded.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given phone number has been - * disabled.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
- * - * @example - * // 'recaptcha-container' is the ID of an element in the DOM. - * var applicationVerifier = new firebase.auth.RecaptchaVerifier( - * 'recaptcha-container'); - * firebase.auth().signInWithPhoneNumber(phoneNumber, applicationVerifier) - * .then(function(confirmationResult) { - * var verificationCode = window.prompt('Please enter the verification ' + - * 'code that was sent to your mobile device.'); - * return confirmationResult.confirm(verificationCode); - * }) - * .catch(function(error) { - * // Handle Errors here. - * }); - * - * @param {string} phoneNumber The user's phone number in E.164 format (e.g. - * +16505550101). - * @param {!firebase.auth.ApplicationVerifier} applicationVerifier - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInWithPhoneNumber = function ( - phoneNumber, - applicationVerifier -) {}; - -/** - * A result from a phone number sign-in, link, or reauthenticate call. - * @interface - */ -firebase.auth.ConfirmationResult = function () {}; - -/** - * The phone number authentication operation's verification ID. This can be used - * along with the verification code to initialize a phone auth credential. - * - * @type {string} - */ -firebase.auth.ConfirmationResult.prototype.verificationId; - -/** - * Finishes a phone number sign-in, link, or reauthentication, given the code - * that was sent to the user's mobile device. - * - *

Error Codes

- *
- *
auth/invalid-verification-code
- *
Thrown if the verification code is not valid.
- *
auth/missing-verification-code
- *
Thrown if the verification code is missing.
- *
- * @param {string} verificationCode - * @return {!firebase.Promise} - */ -firebase.auth.ConfirmationResult.prototype.confirm = function ( - verificationCode -) {}; - -/** - * Asynchronously signs in as an anonymous user. - * - * - * If there is already an anonymous user signed in, that user will be returned; - * otherwise, a new anonymous user identity will be created and returned. - * - *

Error Codes

- *
- *
auth/operation-not-allowed
- *
Thrown if anonymous accounts are not enabled. Enable anonymous accounts - * in the Firebase Console, under the Auth tab.
- *
- * - * @example - * firebase.auth().signInAnonymously().catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * - * if (errorCode === 'auth/operation-not-allowed') { - * alert('You must enable Anonymous auth in the Firebase Console.'); - * } else { - * console.error(error); - * } - * }); - * - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInAnonymously = function () {}; - -/** - * Asynchronously sets the provided user as `currentUser` on the current Auth - * instance. A new instance copy of the user provided will be made and set as - * `currentUser`. - * - * This will trigger {@link firebase.auth.Auth#onAuthStateChanged} and - * {@link firebase.auth.Auth#onIdTokenChanged} listeners like other sign in - * methods. - * - * The operation fails with an error if the user to be updated belongs to a - * different Firebase project. - * - *

Error Codes

- *
- *
auth/invalid-user-token
- *
Thrown if the user to be updated belongs to a diffent Firebase - * project.
- *
auth/user-token-expired
- *
Thrown if the token of the user to be updated is expired.
- *
auth/null-user
- *
Thrown if the user to be updated is null.
- *
auth/tenant-id-mismatch
- *
Thrown if the provided user's tenant ID does not match the - * underlying Auth instance's configured tenant ID
- *
- * - * @param {?firebase.User} user - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.updateCurrentUser = function (user) {}; - -/** - * A structure containing a User, an AuthCredential, the operationType, and - * any additional user information that was returned from the identity provider. - * operationType could be 'signIn' for a sign-in operation, 'link' for a linking - * operation and 'reauthenticate' for a reauthentication operation. - * - * @typedef {{ - * user: ?firebase.User, - * credential: ?firebase.auth.AuthCredential, - * operationType: (?string|undefined), - * additionalUserInfo: (?firebase.auth.AdditionalUserInfo|undefined) - * }} - */ -firebase.auth.UserCredential; - -/** - * A structure containing additional user information from a federated identity - * provider. - * @typedef {{ - * providerId: string, - * profile: ?Object, - * username: (?string|undefined), - * isNewUser: boolean - * }} - */ -firebase.auth.AdditionalUserInfo; - -/** - * Interface representing ID token result obtained from - * {@link firebase.User#getIdTokenResult}. It contains the ID token JWT string - * and other helper properties for getting different data associated with the - * token as well as all the decoded payload claims. - * - * Note that these claims are not to be trusted as they are parsed client side. - * Only server side verification can guarantee the integrity of the token - * claims. - * - * @interface - */ -firebase.auth.IdTokenResult = function () {}; - -/** - * The Firebase Auth ID token JWT string. - * - * @type {string} - */ -firebase.auth.IdTokenResult.prototype.token; - -/** - * The ID token expiration time formatted as a UTC string. - * - * @type {string} - */ -firebase.auth.IdTokenResult.prototype.expirationTime; - -/** - * The authentication time formatted as a UTC string. This is the time the - * user authenticated (signed in) and not the time the token was refreshed. - * - * @type {string} - */ -firebase.auth.IdTokenResult.prototype.authTime; - -/** - * The ID token issued at time formatted as a UTC string. - * - * @type {string} - */ -firebase.auth.IdTokenResult.prototype.issuedAtTime; - -/** - * The sign-in provider through which the ID token was obtained (anonymous, - * custom, phone, password, etc). Note, this does not map to provider IDs. - * - * @type {?string} - */ -firebase.auth.IdTokenResult.prototype.signInProvider; - -/** - * The type of second factor associated with this session, provided the user - * was multi-factor authenticated (eg. phone, etc). - * - * @type {?string} - */ -firebase.auth.IdTokenResult.prototype.signInSecondFactor; - -/** - * The entire payload claims of the ID token including the standard reserved - * claims as well as the custom claims. - * - * @type {!Object} - */ -firebase.auth.IdTokenResult.prototype.claims; - -/** - * Interface representing an Auth instance's settings, currently used for - * enabling/disabling app verification for phone Auth testing. - * - * @interface - */ -firebase.auth.AuthSettings = function () {}; - -/** - * When set, this property disables app verification for the purpose of testing - * phone authentication. For this property to take effect, it needs to be set - * before rendering a reCAPTCHA app verifier. When this is disabled, a - * mock reCAPTCHA is rendered instead. This is useful for manual testing during - * development or for automated integration tests. - * - * In order to use this feature, you will need to - * {@link https://firebase.google.com/docs/auth/web/phone-auth#test-with-whitelisted-phone-numbers - * whitelist your phone number} via the - * Firebase Console. - * - * The default value is false (app verification is enabled). - * - * @type {boolean} - */ -firebase.auth.AuthSettings.prototype.appVerificationDisabledForTesting; - -/** - * Signs out the current user. - * - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signOut = function () {}; - -/** - * An authentication error. - * For method-specific error codes, refer to the specific methods in the - * documentation. For common error codes, check the reference below. Use {@link - * firebase.auth.Error#code} to get the specific error code. For a detailed - * message, use {@link firebase.auth.Error#message}. - * Errors with the code auth/account-exists-with-different-credential - * will have the additional fields email and - * credential which are needed to provide a way to resolve these - * specific errors. Refer to {@link firebase.auth.Auth#signInWithPopup} for more - * information. - * - *

Common Error Codes

- *
- *
auth/app-deleted
- *
Thrown if the instance of FirebaseApp has been deleted.
- *
auth/app-not-authorized
- *
Thrown if the app identified by the domain where it's hosted, is not - * authorized to use Firebase Authentication with the provided API key. - * Review your key configuration in the Google API console.
- *
auth/argument-error
- *
Thrown if a method is called with incorrect arguments.
- *
auth/invalid-api-key
- *
Thrown if the provided API key is invalid. Please check that you have - * copied it correctly from the Firebase Console.
- *
auth/invalid-user-token
- *
Thrown if the user's credential is no longer valid. The user must sign in - * again.
- *
auth/network-request-failed
- *
Thrown if a network error (such as timeout, interrupted connection or - * unreachable host) has occurred.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User#reauthenticateWithCredential} to - * resolve. This does not apply if the user is anonymous.
- *
auth/too-many-requests
- *
Thrown if requests are blocked from a device due to unusual activity. - * Trying again after some delay would unblock.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
auth/user-disabled
- *
Thrown if the user account has been disabled by an administrator. - * Accounts can be enabled or disabled in the Firebase Console, the Auth - * section and Users subsection.
- *
auth/user-token-expired
- *
Thrown if the user's credential has expired. This could also be thrown if - * a user has been deleted. Prompting the user to sign in again should - * resolve this for either case.
- *
auth/web-storage-unsupported
- *
Thrown if the browser does not support web storage or if the user - * disables them.
- *
- * - * @interface - */ -firebase.auth.Error = function () {}; - -/** - * Unique error code. - * - * @type {string} - */ -firebase.auth.Error.prototype.code; - -/** - * Complete error message. - * - * @type {string} - */ -firebase.auth.Error.prototype.message; - -/** - * The account conflict error. - * Refer to {@link firebase.auth.Auth.signInWithPopup} for more information. - * - *

Common Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail} with the - * error.email and then asking the user to sign in using one of the returned - * providers. Once the user is signed in, the original credential retrieved - * from the error.credential can be linked to the user with - * {@link firebase.User.linkWithCredential} to prevent the user from signing - * in again to the original provider via popup or redirect. If you are using - * redirects for sign in, save the credential in session storage and then - * retrieve on redirect and repopulate the credential using for example - * {@link firebase.auth.GoogleAuthProvider.credential} depending on the - * credential provider id and complete the link.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * The fields error.email, error.phoneNumber, and - * error.credential ({@link firebase.auth.AuthCredential}) - * may be provided, depending on the type of credential. You can recover - * from this error by signing in with error.credential directly - * via {@link firebase.auth.Auth.signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth.fetchSignInMethodsForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User.linkWithCredential} the original credential to that - * newly signed in user.
- *
- * - * @interface - * @extends {firebase.auth.Error} - */ -firebase.auth.AuthError = function () {}; - -/** - * The {@link firebase.auth.AuthCredential} that can be used to resolve the - * error. - * - * @type {!firebase.auth.AuthCredential|undefined} - */ -firebase.auth.AuthError.prototype.credential; - -/** - * The email of the user's account used for sign-in/linking. - * - * @type {string|undefined} - */ -firebase.auth.AuthError.prototype.email; - -/** - * The phone number of the user's account used for sign-in/linking. - * - * @type {string|undefined} - */ -firebase.auth.AuthError.prototype.phoneNumber; - -/** - * The tenant ID being used for sign-in/linking. If you use - * {@link firebase.auth.signInWithRedirect} to sign in, you have to - * set the tenant ID on Auth instanace again as the tenant ID is not - * persisted after redirection. - * - * @type {string|undefined} - */ -firebase.auth.AuthError.prototype.tenantId; - -/** - * The error thrown when the user needs to provide a second factor to sign in - * successfully. - * The error code for this error is auth/multi-factor-auth-required. - * This error provides a {@link firebase.auth.MultiFactorResolver} object, - * which you can use to get the second sign-in factor from the user. - * - * @example - * ```javascript - * firebase.auth().signInWithEmailAndPassword() - * .then(function(result) { - * // User signed in. No 2nd factor challenge is needed. - * }) - * .catch(function(error) { - * if (error.code == 'auth/multi-factor-auth-required') { - * var resolver = error.resolver; - * var multiFactorHints = resolver.hints; - * } else { - * // Handle other errors. - * } - * }); - * - * resolver.resolveSignIn(multiFactorAssertion) - * .then(function(userCredential) { - * // User signed in. - * }); - * ``` - * - * @interface - * @extends {firebase.auth.Error} - */ -firebase.auth.MultiFactorError = function () {}; - -/** - * The multi-factor resolver to complete second factor sign-in. - * - * @type {!firebase.auth.MultiFactorResolver} - */ -firebase.auth.MultiFactorError.prototype.resolver; - -// -// List of Auth Providers. -// - -/** - * Interface that represents an auth provider. - * - * @interface - */ -firebase.auth.AuthProvider = function () {}; - -/** @type {string} */ -firebase.auth.AuthProvider.prototype.providerId; - -/** - * Generic OAuth provider. - * - * @example - * // Using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // This gives you the OAuth Access Token for that provider. - * var token = result.credential.accessToken; - * } - * var user = result.user; - * }); - * - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.OAuthProvider('google.com'); - * provider.addScope('profile'); - * provider.addScope('email'); - * firebase.auth().signInWithRedirect(provider); - * - * @example - * // Using a popup. - * var provider = new firebase.auth.OAuthProvider('google.com'); - * provider.addScope('profile'); - * provider.addScope('email'); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // This gives you the OAuth Access Token for that provider. - * var token = result.credential.accessToken; - * // The signed-in user info. - * var user = result.user; - * }); - * - * @see {@link firebase.auth.Auth#onAuthStateChanged} to receive sign in state - * changes. - * @param {string} providerId The associated provider ID, such as `github.com`. - * @constructor - * @implements {firebase.auth.AuthProvider} - */ -firebase.auth.OAuthProvider = function (providerId) {}; - -/** - * Creates a Firebase credential from a generic OAuth provider's access token or - * ID token. The raw nonce is required when an ID token with a nonce field is - * provided. The SHA-256 hash of the raw nonce must match the nonce field in - * the ID token. - * - * @example - * // `googleUser` from the onsuccess Google Sign In callback. - * // Initialize a generate OAuth provider with a `google.com` providerId. - * var provider = new firebase.auth.OAuthProvider('google.com'); - * var credential = provider.credential({ - * idToken: googleUser.getAuthResponse().id_token, - * }); - * firebase.auth().signInWithCredential(credential) - * - * @param {?firebase.auth.OAuthCredentialOptions|string} optionsOrIdToken Either - * the options object containing the ID token, access token and raw nonce or - * the ID token string. - * @param {?string=} accessToken The OAuth access token. - * @return {!firebase.auth.OAuthCredential} The auth provider credential. - */ -firebase.auth.OAuthProvider.prototype.credential = function ( - optionsOrIdToken, - accessToken -) {}; - -/** @type {string} */ -firebase.auth.OAuthProvider.prototype.providerId; - -/** - * @param {string} scope Provider OAuth scope to add. - * @return {!firebase.auth.OAuthProvider} The provider instance. - */ -firebase.auth.OAuthProvider.prototype.addScope = function (scope) {}; - -/** - * Sets the OAuth custom parameters to pass in an OAuth request for popup - * and redirect sign-in operations. - * For a detailed list, check the - * reserved required OAuth 2.0 parameters such as `client_id`, `redirect_uri`, - * `scope`, `response_type` and `state` are not allowed and will be ignored. - * @param {!Object} customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return {!firebase.auth.OAuthProvider} The provider instance. - */ -firebase.auth.OAuthProvider.prototype.setCustomParameters = function ( - customOAuthParameters -) {}; - -/** - * Facebook auth provider. - * - * @example - * // Sign in using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // This gives you a Google Access Token. - * var token = result.credential.accessToken; - * } - * var user = result.user; - * }) - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.FacebookAuthProvider(); - * provider.addScope('user_birthday'); - * firebase.auth().signInWithRedirect(provider); - * - * @example - * // Sign in using a popup. - * var provider = new firebase.auth.FacebookAuthProvider(); - * provider.addScope('user_birthday'); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // This gives you a Facebook Access Token. - * var token = result.credential.accessToken; - * // The signed-in user info. - * var user = result.user; - * }); - * - * @see {@link firebase.auth.Auth#onAuthStateChanged} to receive sign in state - * changes. - * @constructor - * @implements {firebase.auth.AuthProvider} - */ -firebase.auth.FacebookAuthProvider = function () {}; - -/** @type {string} */ -firebase.auth.FacebookAuthProvider.PROVIDER_ID; - -/** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}. - * - * @type {string} - */ -firebase.auth.FacebookAuthProvider.FACEBOOK_SIGN_IN_METHOD; - -/** - * @example - * var cred = firebase.auth.FacebookAuthProvider.credential( - * // `event` from the Facebook auth.authResponseChange callback. - * event.authResponse.accessToken - * ); - * - * @param {string} token Facebook access token. - * @return {!firebase.auth.OAuthCredential} The auth provider credential. - */ -firebase.auth.FacebookAuthProvider.credential = function (token) {}; - -/** @type {string} */ -firebase.auth.FacebookAuthProvider.prototype.providerId; - -/** - * @param {string} scope Facebook OAuth scope. - * @return {!firebase.auth.AuthProvider} The provider instance itself. - */ -firebase.auth.FacebookAuthProvider.prototype.addScope = function (scope) {}; - -/** - * Sets the OAuth custom parameters to pass in a Facebook OAuth request for - * popup and redirect sign-in operations. - * Valid parameters include 'auth_type', 'display' and 'locale'. - * For a detailed list, check the - * {@link https://goo.gl/pve4fo Facebook} - * documentation. - * Reserved required OAuth 2.0 parameters such as 'client_id', 'redirect_uri', - * 'scope', 'response_type' and 'state' are not allowed and will be ignored. - * @param {!Object} customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return {!firebase.auth.AuthProvider} The provider instance itself. - */ -firebase.auth.FacebookAuthProvider.prototype.setCustomParameters = function ( - customOAuthParameters -) {}; - -/** - * Github auth provider. - * - * GitHub requires an OAuth 2.0 redirect, so you can either handle the redirect - * directly, or use the signInWithPopup handler: - * - * @example - * // Using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // This gives you a GitHub Access Token. - * var token = result.credential.accessToken; - * } - * var user = result.user; - * }).catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * // The email of the user's account used. - * var email = error.email; - * // The firebase.auth.AuthCredential type that was used. - * var credential = error.credential; - * if (errorCode === 'auth/account-exists-with-different-credential') { - * alert('You have signed up with a different provider for that email.'); - * // Handle linking here if your app allows it. - * } else { - * console.error(error); - * } - * }); - * - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.GithubAuthProvider(); - * provider.addScope('repo'); - * firebase.auth().signInWithRedirect(provider); - * - * @example - * // With popup. - * var provider = new firebase.auth.GithubAuthProvider(); - * provider.addScope('repo'); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // This gives you a GitHub Access Token. - * var token = result.credential.accessToken; - * // The signed-in user info. - * var user = result.user; - * }).catch(function(error) { - * // Handle Errors here. - * var errorCode = error.code; - * var errorMessage = error.message; - * // The email of the user's account used. - * var email = error.email; - * // The firebase.auth.AuthCredential type that was used. - * var credential = error.credential; - * if (errorCode === 'auth/account-exists-with-different-credential') { - * alert('You have signed up with a different provider for that email.'); - * // Handle linking here if your app allows it. - * } else { - * console.error(error); - * } - * }); - * - * @see {@link firebase.auth.Auth#onAuthStateChanged} to receive sign in state - * changes. - * @constructor - * @implements {firebase.auth.AuthProvider} - */ -firebase.auth.GithubAuthProvider = function () {}; - -/** @type {string} */ -firebase.auth.GithubAuthProvider.PROVIDER_ID; - -/** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}. - * - * @type {string} - */ -firebase.auth.GithubAuthProvider.GITHUB_SIGN_IN_METHOD; - -/** - * @example - * var cred = firebase.auth.FacebookAuthProvider.credential( - * // `event` from the Facebook auth.authResponseChange callback. - * event.authResponse.accessToken - * ); - * - * @param {string} token Github access token. - * @return {!firebase.auth.OAuthCredential} The auth provider credential. - */ -firebase.auth.GithubAuthProvider.credential = function (token) {}; - -/** @type {string} */ -firebase.auth.GithubAuthProvider.prototype.providerId; - -/** - * @param {string} scope Github OAuth scope. - * @return {!firebase.auth.AuthProvider} The provider instance itself. - */ -firebase.auth.GithubAuthProvider.prototype.addScope = function (scope) {}; - -/** - * Sets the OAuth custom parameters to pass in a GitHub OAuth request for popup - * and redirect sign-in operations. - * Valid parameters include 'allow_signup'. - * For a detailed list, check the - * {@link https://developer.github.com/v3/oauth/ GitHub} documentation. - * Reserved required OAuth 2.0 parameters such as 'client_id', 'redirect_uri', - * 'scope', 'response_type' and 'state' are not allowed and will be ignored. - * @param {!Object} customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return {!firebase.auth.AuthProvider} The provider instance itself. - */ -firebase.auth.GithubAuthProvider.prototype.setCustomParameters = function ( - customOAuthParameters -) {}; - -/** - * Google auth provider. - * - * @example - * // Using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // This gives you a Google Access Token. - * var token = result.credential.accessToken; - * } - * var user = result.user; - * }); - * - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.GoogleAuthProvider(); - * provider.addScope('profile'); - * provider.addScope('email'); - * firebase.auth().signInWithRedirect(provider); - * - * @example - * // Using a popup. - * var provider = new firebase.auth.GoogleAuthProvider(); - * provider.addScope('profile'); - * provider.addScope('email'); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // This gives you a Google Access Token. - * var token = result.credential.accessToken; - * // The signed-in user info. - * var user = result.user; - * }); - * - * @see {@link firebase.auth.Auth#onAuthStateChanged} to receive sign in state - * changes. - * @constructor - * @implements {firebase.auth.AuthProvider} - */ -firebase.auth.GoogleAuthProvider = function () {}; - -/** @type {string} */ -firebase.auth.GoogleAuthProvider.PROVIDER_ID; - -/** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}. - * - * @type {string} - */ -firebase.auth.GoogleAuthProvider.GOOGLE_SIGN_IN_METHOD; - -/** - * Creates a credential for Google. At least one of ID token and access token - * is required. - * - * @example - * // `googleUser` from the onsuccess Google Sign In callback. - * var credential = firebase.auth.GoogleAuthProvider.credential( - googleUser.getAuthResponse().id_token); - * firebase.auth().signInWithCredential(credential) - * - * @param {?string=} idToken Google ID token. - * @param {?string=} accessToken Google access token. - * @return {!firebase.auth.OAuthCredential} The auth provider credential. - */ -firebase.auth.GoogleAuthProvider.credential = function ( - idToken, - accessToken -) {}; - -/** @type {string} */ -firebase.auth.GoogleAuthProvider.prototype.providerId; - -/** - * @param {string} scope Google OAuth scope. - * @return {!firebase.auth.AuthProvider} The provider instance itself. - */ -firebase.auth.GoogleAuthProvider.prototype.addScope = function (scope) {}; - -/** - * Sets the OAuth custom parameters to pass in a Google OAuth request for popup - * and redirect sign-in operations. - * Valid parameters include 'hd', 'hl', 'include_granted_scopes', 'login_hint' - * and 'prompt'. - * For a detailed list, check the - * {@link https://goo.gl/Xo01Jm Google} - * documentation. - * Reserved required OAuth 2.0 parameters such as 'client_id', 'redirect_uri', - * 'scope', 'response_type' and 'state' are not allowed and will be ignored. - * @param {!Object} customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return {!firebase.auth.AuthProvider} The provider instance itself. - */ -firebase.auth.GoogleAuthProvider.prototype.setCustomParameters = function ( - customOAuthParameters -) {}; - -/** - * Twitter auth provider. - * - * @example - * // Using a redirect. - * firebase.auth().getRedirectResult().then(function(result) { - * if (result.credential) { - * // For accessing the Twitter API. - * var token = result.credential.accessToken; - * var secret = result.credential.secret; - * } - * var user = result.user; - * }); - * - * // Start a sign in process for an unauthenticated user. - * var provider = new firebase.auth.TwitterAuthProvider(); - * firebase.auth().signInWithRedirect(provider); - * - * @example - * // Using a popup. - * var provider = new firebase.auth.TwitterAuthProvider(); - * firebase.auth().signInWithPopup(provider).then(function(result) { - * // For accessing the Twitter API. - * var token = result.credential.accessToken; - * var secret = result.credential.secret; - * // The signed-in user info. - * var user = result.user; - * }); - * - * @see {@link firebase.auth.Auth#onAuthStateChanged} to receive sign in state - * changes. - * @constructor - * @implements {firebase.auth.AuthProvider} - */ -firebase.auth.TwitterAuthProvider = function () {}; - -/** @type {string} */ -firebase.auth.TwitterAuthProvider.PROVIDER_ID; - -/** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}. - * - * @type {string} - */ -firebase.auth.TwitterAuthProvider.TWITTER_SIGN_IN_METHOD; - -/** - * @param {string} token Twitter access token. - * @param {string} secret Twitter secret. - * @return {!firebase.auth.OAuthCredential} The auth provider credential. - */ -firebase.auth.TwitterAuthProvider.credential = function (token, secret) {}; - -/** @type {string} */ -firebase.auth.TwitterAuthProvider.prototype.providerId; - -/** - * Sets the OAuth custom parameters to pass in a Twitter OAuth request for popup - * and redirect sign-in operations. - * Valid parameters include 'lang'. - * Reserved required OAuth 1.0 parameters such as 'oauth_consumer_key', - * 'oauth_token', 'oauth_signature', etc are not allowed and will be ignored. - * @param {!Object} customOAuthParameters The custom OAuth parameters to pass - * in the OAuth request. - * @return {!firebase.auth.AuthProvider} The provider instance itself. - */ -firebase.auth.TwitterAuthProvider.prototype.setCustomParameters = function ( - customOAuthParameters -) {}; - -/** - * Email and password auth provider implementation. - * - * To authenticate: {@link firebase.auth.Auth#createUserWithEmailAndPassword} - * and {@link firebase.auth.Auth#signInWithEmailAndPassword}. - * - * @constructor - * @implements {firebase.auth.AuthProvider} - */ -firebase.auth.EmailAuthProvider = function () {}; - -/** @type {string} */ -firebase.auth.EmailAuthProvider.PROVIDER_ID; - -/** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}. - * - * @type {string} - */ -firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD; - -/** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}. - * - * @type {string} - */ -firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD; - -/** - * @example - * var cred = firebase.auth.EmailAuthProvider.credential( - * email, - * password - * ); - * - * @param {string} email Email address. - * @param {string} password User account password. - * @return {!firebase.auth.AuthCredential} The auth provider credential. - */ -firebase.auth.EmailAuthProvider.credential = function (email, password) {}; - -/** - * Initialize an `EmailAuthProvider` credential using an email and an email link - * after a sign in with email link operation. - * - * @example - * var cred = firebase.auth.EmailAuthProvider.credentialWithLink( - * email, - * emailLink - * ); - * - * @param {string} email Email address. - * @param {string} emailLink Sign-in email link. - * @return {!firebase.auth.AuthCredential} The auth provider credential. - */ -firebase.auth.EmailAuthProvider.credentialWithLink = function ( - email, - emailLink -) {}; - -/** @type {string} */ -firebase.auth.EmailAuthProvider.prototype.providerId; - -/** - * Phone number auth provider. - * - * @example - * // 'recaptcha-container' is the ID of an element in the DOM. - * var applicationVerifier = new firebase.auth.RecaptchaVerifier( - * 'recaptcha-container'); - * var provider = new firebase.auth.PhoneAuthProvider(); - * provider.verifyPhoneNumber('+16505550101', applicationVerifier) - * .then(function(verificationId) { - * var verificationCode = window.prompt('Please enter the verification ' + - * 'code that was sent to your mobile device.'); - * return firebase.auth.PhoneAuthProvider.credential(verificationId, - * verificationCode); - * }) - * .then(function(phoneCredential) { - * return firebase.auth().signInWithCredential(phoneCredential); - * }); - * - * @constructor - * @param {?firebase.auth.Auth=} auth The Firebase Auth instance in which - * sign-ins should occur. Uses the default Auth instance if unspecified. - * @implements {firebase.auth.AuthProvider} - */ -firebase.auth.PhoneAuthProvider = function (auth) {}; - -/** @type {string} */ -firebase.auth.PhoneAuthProvider.PROVIDER_ID; - -/** - * This corresponds to the sign-in method identifier as returned in - * {@link firebase.auth.Auth#fetchSignInMethodsForEmail}. - * - * @type {string} - */ -firebase.auth.PhoneAuthProvider.PHONE_SIGN_IN_METHOD; - -/** - * Creates a phone auth credential, given the verification ID from - * {@link firebase.auth.PhoneAuthProvider#verifyPhoneNumber} and the code - * that was sent to the user's mobile device. - * - *

Error Codes

- *
- *
auth/missing-verification-code
- *
Thrown if the verification code is missing.
- *
auth/missing-verification-id
- *
Thrown if the verification ID is missing.
- *
- * - * @param {string} verificationId The verification ID returned from - * {@link firebase.auth.PhoneAuthProvider#verifyPhoneNumber}. - * @param {string} verificationCode The verification code sent to the user's - * mobile device. - * @return {!firebase.auth.PhoneAuthCredential} The auth provider credential. - */ -firebase.auth.PhoneAuthProvider.credential = function ( - verificationId, - verificationCode -) {}; - -/** @type {string} */ -firebase.auth.PhoneAuthProvider.prototype.providerId; - -/** - * Starts a phone number authentication flow by sending a verification code to - * the given phone number. Returns an ID that can be passed to - * {@link firebase.auth.PhoneAuthProvider#credential} to identify this flow. - * - * For abuse prevention, this method also requires a - * {@link firebase.auth.ApplicationVerifier}. The Firebase Auth SDK includes - * a reCAPTCHA-based implementation, {@link firebase.auth.RecaptchaVerifier}. - * - *

Error Codes

- *
- *
auth/captcha-check-failed
- *
Thrown if the reCAPTCHA response token was invalid, expired, or if - * this method was called from a non-whitelisted domain.
- *
auth/invalid-phone-number
- *
Thrown if the phone number has an invalid format.
- *
auth/missing-phone-number
- *
Thrown if the phone number is missing.
- *
auth/quota-exceeded
- *
Thrown if the SMS quota for the Firebase project has been exceeded.
- *
auth/user-disabled
- *
Thrown if the user corresponding to the given phone number has been - * disabled.
- *
auth/maximum-second-factor-count-exceeded
- *
Thrown if The maximum allowed number of second factors on a user - * has been exceeded.
- *
auth/second-factor-already-in-use
- *
Thrown if the second factor is already enrolled on this account.
- *
auth/unsupported-first-factor
- *
Thrown if the first factor being used to sign in is not supported.
- *
auth/unverified-email
- *
Thrown if the email of the account is not verified.
- *
- * - * @param {!firebase.auth.PhoneInfoOptions|string} phoneInfoOptions The user's - * {@link firebase.auth.PhoneInfoOptions}. The phone number should be in - * E.164 format (e.g. +16505550101). - * @param {!firebase.auth.ApplicationVerifier} applicationVerifier - * @return {!firebase.Promise} A Promise for the verification ID. - */ -firebase.auth.PhoneAuthProvider.prototype.verifyPhoneNumber = function ( - phoneInfoOptions, - applicationVerifier -) {}; - -/** - * A verifier for domain verification and abuse prevention. Currently, the - * only implementation is {@link firebase.auth.RecaptchaVerifier}. - * @interface - */ -firebase.auth.ApplicationVerifier = function () {}; - -/** - * Identifies the type of application verifier (e.g. "recaptcha"). - * @type {string} - */ -firebase.auth.ApplicationVerifier.prototype.type; - -/** - * Executes the verification process. - * @return {!firebase.Promise} A Promise for a token that can be used to - * assert the validity of a request. - */ -firebase.auth.ApplicationVerifier.prototype.verify = function () {}; - -/** - * The interface for asserting ownership of a second factor. This is used to - * facilitate enrollment of a second factor on an existing user - * or sign-in of a user who already verified the first factor. - * - * @interface - */ -firebase.auth.MultiFactorAssertion = function () {}; - -/** - * The identifier of the second factor. - * @type {string} - */ -firebase.auth.MultiFactorAssertion.prototype.factorId; - -/** - * The interface for asserting ownership of a phone second factor. - * - * @interface - * @extends {firebase.auth.MultiFactorAssertion} - */ -firebase.auth.PhoneMultiFactorAssertion = function () {}; - -/** - * The interface used to initialize a - * {@link firebase.auth.PhoneMultiFactorAssertion}. - * - * @interface - */ -firebase.auth.PhoneMultiFactorGenerator = function () {}; - -/** - * The identifier of the phone second factor: `phone`. - * @type {string} - */ -firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID; - -/** - * Initializes the {@link firebase.auth.PhoneMultiFactorAssertion} to confirm - * ownership of the phone second factor. - * - * @param {!firebase.auth.PhoneAuthCredential} phoneAuthCredential The phone - * Auth credential. - * @return {!firebase.auth.PhoneMultiFactorAssertion} - */ -firebase.auth.PhoneMultiFactorGenerator.assertion = function ( - phoneAuthCredential -) {}; - -/** - * A structure containing the information of a second factor entity. - * - * @interface - */ -firebase.auth.MultiFactorInfo = function () {}; - -/** - * The multi-factor enrollment ID. - * @type {string} - */ -firebase.auth.MultiFactorInfo.prototype.uid; - -/** - * The user friendly name of the current second factor. - * @type {?string|undefined} - */ -firebase.auth.MultiFactorInfo.prototype.displayName; - -/** - * The enrollment date of the second factor formatted as a UTC string. - * @type {string} - */ -firebase.auth.MultiFactorInfo.prototype.enrollmentTime; - -/** - * The identifier of the second factor. - * @type {string} - */ -firebase.auth.MultiFactorInfo.prototype.factorId; - -/** - * The subclass of the `MultiFactorInfo` interface for phone number second - * factors. The factorId of this second factor is - * {@link firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID}. - * - * @interface - * @extends {firebase.auth.MultiFactorInfo} - */ -firebase.auth.PhoneMultiFactorInfo = function () {}; - -/** - * The phone number associated with the current second factor. - * @type {string} - */ -firebase.auth.PhoneMultiFactorInfo.prototype.phoneNumber; - -/** - * The phone info options for single-factor sign-in. Only phone number is - * required. - * - * @typedef {{ - * phoneNumber: string - * }} - */ -firebase.auth.PhoneSingleFactorInfoOptions; - -/** - * The phone info options for multi-factor enrollment. Phone number and - * multi-factor session are required. - * - * @typedef {{ - * phoneNumber: string, - * session: !firebase.auth.MultiFactorSession - * }} - */ -firebase.auth.PhoneMultiFactorEnrollInfoOptions; - -/** - * The phone info options for multi-factor sign-in. Either multi-factor hint or - * multi-factor UID and multi-factor session are required. - * - * @typedef {{ - * multiFactorHint: !firebase.auth.MultiFactorInfo, - * session: !firebase.auth.MultiFactorSession - * }|{ - * multiFactorUid: string, - * session: !firebase.auth.MultiFactorSession - * }} - */ -firebase.auth.PhoneMultiFactorSignInInfoOptions; - -/** - * The information required to verify the ownership of a phone number. The - * information that's required depends on whether you are doing single-factor - * sign-in, multi-factor enrollment or multi-factor sign-in. - * - * @typedef { - * !firebase.auth.PhoneSingleFactorInfoOptions| - * !firebase.auth.PhoneMultiFactorEnrollInfoOptions| - * !firebase.auth.PhoneMultiFactorSignInInfoOptions - * } - */ -firebase.auth.PhoneInfoOptions; - -/** - * The interface used to facilitate recovery from - * {@link firebase.auth.MultiFactorError} when a user needs to provide a second - * factor to sign in. - * - * @example - * ```javascript - * firebase.auth().signInWithEmailAndPassword() - * .then(function(result) { - * // User signed in. No 2nd factor challenge is needed. - * }) - * .catch(function(error) { - * if (error.code == 'auth/multi-factor-auth-required') { - * var resolver = error.resolver; - * // Show UI to let user select second factor. - * var multiFactorHints = resolver.hints; - * } else { - * // Handle other errors. - * } - * }); - * - * // The enrolled second factors that can be used to complete - * // sign-in are returned in the `MultiFactorResolver.hints` list. - * // UI needs to be presented to allow the user to select a second factor - * // from that list. - * - * var selectedHint = // ; selected from multiFactorHints - * var phoneAuthProvider = new firebase.auth.PhoneAuthProvider(); - * var phoneInfoOptions = { - * multiFactorHint: selectedHint, - * session: resolver.session - * }; - * phoneAuthProvider.verifyPhoneNumber( - * phoneInfoOptions, - * appVerifier - * ).then(function(verificationId) { - * // store verificationID and show UI to let user enter verification code. - * }); - * - * // UI to enter verification code and continue. - * // Continue button click handler - * var phoneAuthCredential = - * firebase.auth.PhoneAuthProvider.credential( - * verificationId, verificationCode); - * var multiFactorAssertion = - * firebase.auth.PhoneMultiFactorGenerator.assertion(phoneAuthCredential); - * resolver.resolveSignIn(multiFactorAssertion) - * .then(function(userCredential) { - * // User signed in. - * }); - * ``` - * @interface - */ -firebase.auth.MultiFactorResolver = function () {}; - -/** - * The Auth instance used to sign in with the first factor. - * @type {!firebase.auth.Auth} - */ -firebase.auth.MultiFactorResolver.prototype.auth; - -/** - * The session identifier for the current sign-in flow, which can be used - * to complete the second factor sign-in. - * @type {!firebase.auth.MultiFactorSession} - */ -firebase.auth.MultiFactorResolver.prototype.session; - -/** - * The list of hints for the second factors needed to complete the sign-in - * for the current session. - * @type {!Array} - */ -firebase.auth.MultiFactorResolver.prototype.hints; - -/** - * A helper function to help users complete sign in with a second factor - * using an {@link firebase.auth.MultiFactorAssertion} confirming the user - * successfully completed the second factor challenge. - * - *

Error Codes

- *
- *
auth/invalid-verification-code
- *
Thrown if the verification code is not valid.
- *
auth/missing-verification-code
- *
Thrown if the verification code is missing.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
auth/missing-verification-id
- *
Thrown if the verification ID is missing.
- *
auth/code-expired
- *
Thrown if the verification code has expired.
- *
auth/invalid-multi-factor-session
- *
Thrown if the request does not contain a valid proof of first factor - * successful sign-in.
- *
auth/missing-multi-factor-session
- *
Thrown if The request is missing proof of first factor successful - * sign-in.
- *
- * - * @param {!firebase.auth.MultiFactorAssertion} assertion The multi-factor - * assertion to resolve sign-in with. - * @return {!firebase.Promise} The promise that - * resolves with the user credential object. - */ -firebase.auth.MultiFactorResolver.prototype.resolveSignIn = function ( - assertion -) {}; - -/** - * The multi-factor session object used for enrolling a second factor on a - * user or helping sign in an enrolled user with a second factor. - * - * @interface - */ -firebase.auth.MultiFactorSession = function () {}; - -/** - * This is the interface that defines the multi-factor related properties and - * operations pertaining to a {@link firebase.User}. - * - * @interface - */ -firebase.User.MultiFactorUser = function () {}; - -/** - * Returns a list of the user's enrolled second factors. - * @type {!Array} - */ -firebase.User.MultiFactorUser.prototype.enrolledFactors; - -/** - * Enrolls a second factor as identified by the - * {@link firebase.auth.MultiFactorAssertion} for the current user. - * On resolution, the user tokens are updated to reflect the change in the - * JWT payload. - * Accepts an additional display name parameter used to identify the second - * factor to the end user. - * Recent re-authentication is required for this operation to succeed. - * On successful enrollment, existing Firebase sessions (refresh tokens) are - * revoked. When a new factor is enrolled, an email notification is sent - * to the user’s email. - * - *

Error Codes

- *
- *
auth/invalid-verification-code
- *
Thrown if the verification code is not valid.
- *
auth/missing-verification-code
- *
Thrown if the verification code is missing.
- *
auth/invalid-verification-id
- *
Thrown if the credential is a - * {@link firebase.auth.PhoneAuthProvider.credential} and the verification - * ID of the credential is not valid.
- *
auth/missing-verification-id
- *
Thrown if the verification ID is missing.
- *
auth/code-expired
- *
Thrown if the verification code has expired.
- *
auth/maximum-second-factor-count-exceeded
- *
Thrown if The maximum allowed number of second factors on a user - * has been exceeded.
- *
auth/second-factor-already-in-use
- *
Thrown if the second factor is already enrolled on this account.
- *
auth/unsupported-first-factor
- *
Thrown if the first factor being used to sign in is not supported.
- *
auth/unverified-email
- *
Thrown if the email of the account is not verified.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User.reauthenticateWithCredential} to - * resolve.
- *
- * - * @example - * ```javascript - * firebase.auth().currentUser.multiFactor.getSession() - * .then(function(multiFactorSession) { - * // Send verification code - * var phoneAuthProvider = new firebase.auth.PhoneAuthProvider(); - * var phoneInfoOptions = { - * phoneNumber: phoneNumber, - * session: multiFactorSession - * }; - * return phoneAuthProvider.verifyPhoneNumber( - * phoneInfoOptions, appVerifier); - * }).then(function(verificationId) { - * // Store verificationID and show UI to let user enter verification - * // code. - * }); - * - * var phoneAuthCredential = - * firebase.auth.PhoneAuthProvider.credential( - * verificationId, verificationCode); - * var multiFactorAssertion = - * firebase.auth.PhoneMultiFactorGenerator.assertion(phoneAuthCredential); - * firebase.auth().currentUser.multiFactor.enroll(multiFactorAssertion) - * .then(function() { - * // Second factor enrolled. - * }); - * ``` - * - * @param {!firebase.auth.MultiFactorAssertion} assertion The multi-factor - * assertion to enroll with. - * @param {?string=} displayName The display name of the second factor. - * @return {!firebase.Promise} - */ -firebase.User.MultiFactorUser.prototype.enroll = function ( - assertion, - displayName -) {}; - -/** - * Returns the session identifier for a second factor enrollment operation. - * This is used to identify the current user trying to enroll a second factor. - * - *

Error Codes

- *
- *
auth/user-token-expired
- *
Thrown if the token of the user is expired.
- *
- * - * @return {!firebase.Promise} The promise - * that resolves with the {@link firebase.auth.MultiFactorSession}. - */ -firebase.User.MultiFactorUser.prototype.getSession = function () {}; - -/** - * Unenrolls the specified second factor. To specify the factor to remove, pass - * a {@link firebase.auth.MultiFactorInfo} object - * (retrieved from enrolledFactors()) - * or the factor's UID string. - * Sessions are not revoked when the account is downgraded. An email - * notification is likely to be sent to the user notifying them of the change. - * Recent re-authentication is required for this operation to succeed. - * When an existing factor is unenrolled, an email notification is sent to the - * user’s email. - * - *

Error Codes

- *
- *
auth/multi-factor-info-not-found
- *
Thrown if the user does not have a second factor matching the - * identifier provided.
- *
auth/requires-recent-login
- *
Thrown if the user's last sign-in time does not meet the security - * threshold. Use {@link firebase.User.reauthenticateWithCredential} to - * resolve.
- *
- * - * @example - * ```javascript - * var options = firebase.auth().currentUser.multiFactor.enrolledFactors; - * // Present user the option to unenroll. - * return firebase.auth().currentUser.multiFactor.unenroll(options[i]) - * .then(function() { - * // User successfully unenrolled selected factor. - * }).catch(function(error) { - * // Handler error. - * }); - * ``` - * - * @param {!firebase.auth.MultiFactorInfo|string} option The multi-factor - * option to unenroll. - * @return {!firebase.Promise} - */ -firebase.User.MultiFactorUser.prototype.unenroll = function (option) {}; diff --git a/packages/firebase/externs/firebase-client-auth-externs.js b/packages/firebase/externs/firebase-client-auth-externs.js deleted file mode 100644 index 3cd0a87774f..00000000000 --- a/packages/firebase/externs/firebase-client-auth-externs.js +++ /dev/null @@ -1,502 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase client Auth API. - * @externs - */ - -/** - * Links the authenticated provider to the user account using a pop-up based - * OAuth flow. - * - * If the linking is successful, the returned result will contain the user - * and the provider's credential. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/cancelled-popup-request
- *
Thrown if successive popup operations are triggered. Only one popup - * request is allowed at one time on a user or an auth instance. All the - * popups would fail with this error except for the last one.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * An error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. You can - * recover from this error by signing in with that credential directly via - * {@link firebase.auth.Auth#signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth#fetchProvidersForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User#linkWithCredential} the original credential to that - * newly signed in user.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/popup-blocked
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
Thrown if the popup was blocked by the browser, typically when this - * operation is triggered outside of a click handler.
- *
auth/popup-closed-by-user
- *
Thrown if the popup window is closed by the user without completing the - * sign in to the provider.
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @example - * // Creates the provider object. - * var provider = new firebase.auth.FacebookAuthProvider(); - * // You can add additional scopes to the provider: - * provider.addScope('email'); - * provider.addScope('user_friends'); - * // Link with popup: - * user.linkWithPopup(provider).then(function(result) { - * // The firebase.User instance: - * var user = result.user; - * // The Facebook firebase.auth.AuthCredential containing the Facebook - * // access token: - * var credential = result.credential; - * }, function(error) { - * // An error happened. - * }); - * - * @param {!firebase.auth.AuthProvider} provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - * @return {!firebase.Promise} - */ -firebase.User.prototype.linkWithPopup = function (provider) {}; - -/** - * Links the authenticated provider to the user account using a full-page - * redirect flow. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/provider-already-linked
- *
Thrown if the provider has already been linked to the user. This error is - * thrown even if this is not the same provider's account that is currently - * linked to the user.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @param {!firebase.auth.AuthProvider} provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - * @return {!firebase.Promise} - */ -firebase.User.prototype.linkWithRedirect = function (provider) {}; - -/** - * Authenticates a Firebase client using a popup-based OAuth authentication - * flow. - * - * If succeeds, returns the signed in user along with the provider's credential. - * If sign in was unsuccessful, returns an error object containing additional - * information about the error. - * - *

Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth#fetchProvidersForEmail} with the error.email - * and then asking the user to sign in using one of the returned providers. - * Once the user is signed in, the original credential retrieved from the - * error.credential can be linked to the user with - * {@link firebase.User#linkWithCredential} to prevent the user from signing - * in again to the original provider via popup or redirect. If you are using - * redirects for sign in, save the credential in session storage and then - * retrieve on redirect and repopulate the credential using for example - * {@link firebase.auth.GoogleAuthProvider#credential} depending on the - * credential provider id and complete the link.
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/cancelled-popup-request
- *
Thrown if successive popup operations are triggered. Only one popup - * request is allowed at one time. All the popups would fail with this error - * except for the last one.
- *
auth/operation-not-allowed
- *
Thrown if the type of account corresponding to the credential - * is not enabled. Enable the account type in the Firebase Console, under - * the Auth tab.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/popup-blocked
- *
Thrown if the popup was blocked by the browser, typically when this - * operation is triggered outside of a click handler.
- *
auth/popup-closed-by-user
- *
Thrown if the popup window is closed by the user without completing the - * sign in to the provider.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @example - * // Creates the provider object. - * var provider = new firebase.auth.FacebookAuthProvider(); - * // You can add additional scopes to the provider: - * provider.addScope('email'); - * provider.addScope('user_friends'); - * // Sign in with popup: - * auth.signInWithPopup(provider).then(function(result) { - * // The firebase.User instance: - * var user = result.user; - * // The Facebook firebase.auth.AuthCredential containing the Facebook - * // access token: - * var credential = result.credential; - * }, function(error) { - * // The provider's account email, can be used in case of - * // auth/account-exists-with-different-credential to fetch the providers - * // linked to the email: - * var email = error.email; - * // The provider's credential: - * var credential = error.credential; - * // In case of auth/account-exists-with-different-credential error, - * // you can fetch the providers using this: - * if (error.code === 'auth/account-exists-with-different-credential') { - * auth.fetchProvidersForEmail(email).then(function(providers) { - * // The returned 'providers' is a list of the available providers - * // linked to the email address. Please refer to the guide for a more - * // complete explanation on how to recover from this error. - * }); - * } - * }); - * - * @param {!firebase.auth.AuthProvider} provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInWithPopup = function (provider) {}; - -/** - * Authenticates a Firebase client using a full-page redirect flow. To handle - * the results and errors for this operation, refer to {@link - * firebase.auth.Auth#getRedirectResult}. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @param {!firebase.auth.AuthProvider} provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.signInWithRedirect = function (provider) {}; - -/** - * Reauthenticates the current user with the specified provider using a pop-up - * based OAuth flow. - * - * If the reauthentication is successful, the returned result will contain the - * user and the provider's credential. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/cancelled-popup-request
- *
Thrown if successive popup operations are triggered. Only one popup - * request is allowed at one time on a user or an auth instance. All the - * popups would fail with this error except for the last one.
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/operation-not-allowed
- *
Thrown if you have not enabled the provider in the Firebase Console. Go - * to the Firebase Console for your project, in the Auth section and the - * Sign in Method tab and configure the provider.
- *
auth/popup-blocked
- *
Thrown if the popup was blocked by the browser, typically when this - * operation is triggered outside of a click handler.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/popup-closed-by-user
- *
Thrown if the popup window is closed by the user without completing the - * sign in to the provider.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @example - * // Creates the provider object. - * var provider = new firebase.auth.FacebookAuthProvider(); - * // You can add additional scopes to the provider: - * provider.addScope('email'); - * provider.addScope('user_friends'); - * // Reauthenticate with popup: - * user.reauthenticateWithPopup(provider).then(function(result) { - * // The firebase.User instance: - * var user = result.user; - * // The Facebook firebase.auth.AuthCredential containing the Facebook - * // access token: - * var credential = result.credential; - * }, function(error) { - * // An error happened. - * }); - * - * @param {!firebase.auth.AuthProvider} provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - * @return {!firebase.Promise} - */ -firebase.User.prototype.reauthenticateWithPopup = function (provider) {}; - -/** - * Reauthenticates the current user with the specified OAuth provider using a - * full-page redirect flow. - * - *

Error Codes

- *
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/user-mismatch
- *
Thrown if the credential given does not correspond to the user.
- *
auth/unauthorized-domain
- *
Thrown if the app domain is not authorized for OAuth operations for your - * Firebase project. Edit the list of authorized domains from the Firebase - * console.
- *
- * - * @param {!firebase.auth.AuthProvider} provider The provider to authenticate. - * The provider has to be an OAuth provider. Non-OAuth providers like {@link - * firebase.auth.EmailAuthProvider} will throw an error. - * @return {!firebase.Promise} - */ -firebase.User.prototype.reauthenticateWithRedirect = function (provider) {}; - -/** - * Returns a UserCredential from the redirect-based sign-in flow. - * - * If sign-in succeeded, returns the signed in user. If sign-in was - * unsuccessful, fails with an error. If no redirect operation was called, - * returns a UserCredential with a null User. - * - *

Error Codes

- *
- *
auth/account-exists-with-different-credential
- *
Thrown if there already exists an account with the email address - * asserted by the credential. Resolve this by calling - * {@link firebase.auth.Auth#fetchProvidersForEmail} with the error.email - * and then asking the user to sign in using one of the returned providers. - * Once the user is signed in, the original credential retrieved from the - * error.credential can be linked to the user with - * {@link firebase.User#linkWithCredential} to prevent the user from signing - * in again to the original provider via popup or redirect. If you are using - * redirects for sign in, save the credential in session storage and then - * retrieve on redirect and repopulate the credential using for example - * {@link firebase.auth.GoogleAuthProvider#credential} depending on the - * credential provider id and complete the link.
- *
auth/auth-domain-config-required
- *
Thrown if authDomain configuration is not provided when calling - * firebase.initializeApp(). Check Firebase Console for instructions on - * determining and passing that field.
- *
auth/credential-already-in-use
- *
Thrown if the account corresponding to the credential already exists - * among your users, or is already linked to a Firebase User. - * For example, this error could be thrown if you are upgrading an anonymous - * user to a Google user by linking a Google credential to it and the Google - * credential used is already associated with an existing Firebase Google - * user. - * An error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. You can - * recover from this error by signing in with that credential directly via - * {@link firebase.auth.Auth#signInWithCredential}.
- *
auth/email-already-in-use
- *
Thrown if the email corresponding to the credential already exists - * among your users. When thrown while linking a credential to an existing - * user, an error.email and error.credential - * ({@link firebase.auth.AuthCredential}) fields are also provided. - * You have to link the credential to the existing user with that email if - * you wish to continue signing in with that credential. To do so, call - * {@link firebase.auth.Auth#fetchProvidersForEmail}, sign in to - * error.email via one of the providers returned and then - * {@link firebase.User#linkWithCredential} the original credential to that - * newly signed in user.
- *
auth/operation-not-allowed
- *
Thrown if the type of account corresponding to the credential - * is not enabled. Enable the account type in the Firebase Console, under - * the Auth tab.
- *
auth/operation-not-supported-in-this-environment
- *
Thrown if this operation is not supported in the environment your - * application is running on. "location.protocol" must be http or https. - *
- *
auth/timeout
- *
Thrown typically if the app domain is not authorized for OAuth operations - * for your Firebase project. Edit the list of authorized domains from the - * Firebase console.
- *
- * - * @example - * // First, we perform the signInWithRedirect. - * // Creates the provider object. - * var provider = new firebase.auth.FacebookAuthProvider(); - * // You can add additional scopes to the provider: - * provider.addScope('email'); - * provider.addScope('user_friends'); - * // Sign in with redirect: - * auth.signInWithRedirect(provider) - * //////////////////////////////////////////////////////////// - * // The user is redirected to the provider's sign in flow... - * //////////////////////////////////////////////////////////// - * // Then redirected back to the app, where we check the redirect result: - * auth.getRedirectResult().then(function(result) { - * // The firebase.User instance: - * var user = result.user; - * // The Facebook firebase.auth.AuthCredential containing the Facebook - * // access token: - * var credential = result.credential; - * // As this API can be used for sign-in, linking and reauthentication, - * // check the operationType to determine what triggered this redirect - * // operation. - * var operationType = result.operationType; - * }, function(error) { - * // The provider's account email, can be used in case of - * // auth/account-exists-with-different-credential to fetch the providers - * // linked to the email: - * var email = error.email; - * // The provider's credential: - * var credential = error.credential; - * // In case of auth/account-exists-with-different-credential error, - * // you can fetch the providers using this: - * if (error.code === 'auth/account-exists-with-different-credential') { - * auth.fetchProvidersForEmail(email).then(function(providers) { - * // The returned 'providers' is a list of the available providers - * // linked to the email address. Please refer to the guide for a more - * // complete explanation on how to recover from this error. - * }); - * } - * }); - * - * @return {!firebase.Promise} - */ -firebase.auth.Auth.prototype.getRedirectResult = function () {}; - -/** - * An {@link https://www.google.com/recaptcha/ reCAPTCHA}-based application - * verifier. - * @param {!Element|string} container The reCAPTCHA container parameter. This - * has different meaning depending on whether the reCAPTCHA is hidden or - * visible. For a visible reCAPTCHA the container must be empty. If a string - * is used, it has to correspond to an element ID. The corresponding element - * must also must be in the DOM at the time of initialization. - * @param {?Object=} parameters The optional reCAPTCHA parameters. Check the - * reCAPTCHA docs for a comprehensive list. All parameters are accepted - * except for the sitekey. Firebase Auth backend provisions a reCAPTCHA for - * each project and will configure this upon rendering. For an invisible - * reCAPTCHA, a size key must have the value 'invisible'. - * @param {?firebase.app.App=} app The corresponding Firebase app. If none is - * provided, the default Firebase App instance is used. A Firebase App - * instance must be initialized with an API key, otherwise an error will be - * thrown. - * @constructor - * @implements {firebase.auth.ApplicationVerifier} - */ -firebase.auth.RecaptchaVerifier = function (container, parameters, app) {}; - -/** - * The application verifier type. For a reCAPTCHA verifier, this is 'recaptcha'. - * @type {string} - */ -firebase.auth.RecaptchaVerifier.prototype.type; - -/** - * Clears the reCAPTCHA widget from the page and destroys the current instance. - */ -firebase.auth.RecaptchaVerifier.prototype.clear = function () {}; - -/** - * Renders the reCAPTCHA widget on the page. - * @return {!firebase.Promise} A Promise that resolves with the - * reCAPTCHA widget ID. - */ -firebase.auth.RecaptchaVerifier.prototype.render = function () {}; - -/** - * Waits for the user to solve the reCAPTCHA and resolves with the reCAPTCHA - * token. - * @return {!firebase.Promise} A Promise for the reCAPTCHA token. - * @override - */ -firebase.auth.RecaptchaVerifier.prototype.verify = function () {}; diff --git a/packages/firebase/externs/firebase-database-externs.js b/packages/firebase/externs/firebase-database-externs.js deleted file mode 100644 index a94806b6488..00000000000 --- a/packages/firebase/externs/firebase-database-externs.js +++ /dev/null @@ -1,1682 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase Database API. - * @externs - */ - -/** - * Gets the {@link firebase.database.Database `Database`} service for the - * default app or a given app. - * - * `firebase.database()` can be called with no arguments to access the default - * app's {@link firebase.database.Database `Database`} service or as - * `firebase.database(app)` to access the - * {@link firebase.database.Database `Database`} service associated with a - * specific app. - * - * `firebase.database` is also a namespace that can be used to access global - * constants and methods associated with the `Database` service. - * - * @example - * // Get the Database service for the default app - * var defaultDatabase = firebase.database(); - * - * @example - * // Get the Database service for a specific app - * var otherDatabase = firebase.database(app); - * - * @namespace - * @param {!firebase.app.App=} app Optional app whose Database service to - * return. If not provided, the default Database service will be returned. - * @return {!firebase.database.Database} The default Database service if no app - * is provided or the Database service associated with the provided app. - */ -firebase.database = function (app) {}; - -/** - * Gets the {@link firebase.database.Database `Database`} service for the - * current app. - * - * @example - * var database = app.database(); - * // The above is shorthand for: - * // var database = firebase.database(app); - * - * @return {!firebase.database.Database} - */ -firebase.app.App.prototype.database = function () {}; - -/** - * The Firebase Database service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.database `firebase.database()`}. - * - * See - * {@link - * https://firebase.google.com/docs/database/web/start/ - * Installation & Setup in JavaScript} - * for a full guide on how to use the Firebase Database service. - * - * @interface - */ -firebase.database.Database = function () {}; - -/** - * Logs debugging information to the console. - * - * @example - * // Enable logging - * firebase.database.enableLogging(true); - * - * @example - * // Disable logging - * firebase.database.enableLogging(false); - * - * @example - * // Enable logging across page refreshes - * firebase.database.enableLogging(true, true); - * - * @example - * // Provide custom logger which prefixes log statements with "[FIREBASE]" - * firebase.database.enableLogging(function(message) { - * console.log("[FIREBASE]", message); - * }); - * - * @param {(boolean|function(string))=} logger Enables logging if `true`; - * disables logging if `false`. You can also provide a custom logger function - * to control how things get logged. - * @param {boolean=} persistent Remembers the logging state between page - * refreshes if `true`. - */ -firebase.database.enableLogging = function (logger, persistent) {}; - -/** - * @namespace - */ -firebase.database.ServerValue = {}; - -/** - * A placeholder value for auto-populating the current timestamp (time - * since the Unix epoch, in milliseconds) as determined by the Firebase - * servers. - * - * @example - * var sessionsRef = firebase.database().ref("sessions"); - * sessionsRef.push({ - * startedAt: firebase.database.ServerValue.TIMESTAMP - * }); - * - * @const {!Object} - */ -firebase.database.ServerValue.TIMESTAMP; - -/** - * The {@link firebase.app.App app} associated with the `Database` service - * instance. - * - * @example - * var app = database.app; - * - * @type {!firebase.app.App} - */ -firebase.database.Database.prototype.app; - -/** - * Returns a `Reference` representing the location in the Database - * corresponding to the provided path. If no path is provided, the `Reference` - * will point to the root of the Database. - * - * @example - * // Get a reference to the root of the Database - * var rootRef = firebase.database().ref(); - * - * @example - * // Get a reference to the /users/ada node - * var adaRef = firebase.database().ref("users/ada"); - * // The above is shorthand for the following operations: - * //var rootRef = firebase.database().ref(); - * //var adaRef = rootRef.child("users/ada"); - * - * @param {string=} path Optional path representing the location the returned - * `Reference` will point. If not provided, the returned `Reference` will - * point to the root of the Database. - * @return {!firebase.database.Reference} If a path is provided, a `Reference` - * pointing to the provided path. Otherwise, a `Reference` pointing to the - * root of the Database. - */ -firebase.database.Database.prototype.ref = function (path) {}; - -/** - * Returns a `Reference` representing the location in the Database - * corresponding to the provided Firebase URL. - * - * An exception is thrown if the URL is not a valid Firebase Database URL or it - * has a different domain than the current `Database` instance. - * - * Note that all query parameters (`orderBy`, `limitToLast`, etc.) are ignored - * and are not applied to the returned `Reference`. - * - * @example - * // Get a reference to the root of the Database - * var rootRef = firebase.database().ref("https://.firebaseio.com"); - * - * @example - * // Get a reference to the /users/ada node - * var adaRef = firebase.database().ref("https://.firebaseio.com/users/ada"); - * - * @param {string} url The Firebase URL at which the returned `Reference` will - * point. - * @return {!firebase.database.Reference} A `Reference` pointing to the provided - * Firebase URL. - */ -firebase.database.Database.prototype.refFromURL = function (url) {}; - -/** - * Disconnects from the server (all Database operations will be completed - * offline). - * - * The client automatically maintains a persistent connection to the Database - * server, which will remain active indefinitely and reconnect when - * disconnected. However, the `goOffline()` and `goOnline()` methods may be used - * to control the client connection in cases where a persistent connection is - * undesirable. - * - * While offline, the client will no longer receive data updates from the - * Database. However, all Database operations performed locally will continue to - * immediately fire events, allowing your application to continue behaving - * normally. Additionally, each operation performed locally will automatically - * be queued and retried upon reconnection to the Database server. - * - * To reconnect to the Database and begin receiving remote events, see - * `goOnline()`. - * - * @example - * firebase.database().goOffline(); - */ -firebase.database.Database.prototype.goOffline = function () {}; - -/** - * Reconnects to the server and synchronizes the offline Database state - * with the server state. - * - * This method should be used after disabling the active connection with - * `goOffline()`. Once reconnected, the client will transmit the proper data - * and fire the appropriate events so that your client "catches up" - * automatically. - * - * @example - * firebase.database().goOnline(); - */ -firebase.database.Database.prototype.goOnline = function () {}; - -/** - * A `Reference` represents a specific location in your Database and can be used - * for reading or writing data to that Database location. - * - * You can reference the root or child location in your Database by calling - * `firebase.database().ref()` or `firebase.database().ref("child/path")`. - * - * Writing is done with the `set()` method and reading can be done with the - * `on()` method. See - * {@link - * https://firebase.google.com/docs/database/web/read-and-write - * Read and Write Data on the Web} - * - * @interface - * @extends {firebase.database.Query} - */ -firebase.database.Reference = function () {}; - -/** - * The last part of the `Reference`'s path. - * - * For example, `"ada"` is the key for - * `https://.firebaseio.com/users/ada`. - * - * The key of a root `Reference` is `null`. - * - * @example - * // The key of a root reference is null - * var rootRef = firebase.database().ref(); - * var key = rootRef.key; // key === null - * - * @example - * // The key of any non-root reference is the last token in the path - * var adaRef = firebase.database().ref("users/ada"); - * var key = adaRef.key; // key === "ada" - * key = adaRef.child("name/last").key; // key === "last" - * - * @type {string|null} - */ -firebase.database.Reference.prototype.key; - -/** - * Gets a `Reference` for the location at the specified relative path. - * - * The relative path can either be a simple child name (for example, "ada") or - * a deeper slash-separated path (for example, "ada/name/first"). - * - * @example - * var usersRef = firebase.database().ref('users'); - * var adaRef = usersRef.child('ada'); - * var adaFirstNameRef = adaRef.child('name/first'); - * var path = adaFirstNameRef.toString(); - * // path is now 'https://sample-app.firebaseio.com/users/ada/name/first' - * - * @param {string} path A relative path from this location to the desired child - * location. - * @return {!firebase.database.Reference} The specified child location. - */ -firebase.database.Reference.prototype.child = function (path) {}; - -/** - * The parent location of a `Reference`. - * - * The parent of a root `Reference` is `null`. - * - * @example - * // The parent of a root reference is null - * var rootRef = firebase.database().ref(); - * parent = rootRef.parent; // parent === null - * - * @example - * // The parent of any non-root reference is the parent location - * var usersRef = firebase.database().ref("users"); - * var adaRef = firebase.database().ref("users/ada"); - * // usersRef and adaRef.parent represent the same location - * - * @type {?firebase.database.Reference} - */ -firebase.database.Reference.prototype.parent; - -/** - * The root `Reference` of the Database. - * - * @example - * // The root of a root reference is itself - * var rootRef = firebase.database().ref(); - * // rootRef and rootRef.root represent the same location - * - * @example - * // The root of any non-root reference is the root location - * var adaRef = firebase.database().ref("users/ada"); - * // rootRef and adaRef.root represent the same location - * - * @type {!firebase.database.Reference} - */ -firebase.database.Reference.prototype.root; - -/** - * @type {string} - */ -firebase.database.Reference.prototype.path; - -/** - * Writes data to this Database location. - * - * This will overwrite any data at this location and all child locations. - * - * The effect of the write will be visible immediately, and the corresponding - * events ("value", "child_added", etc.) will be triggered. Synchronization of - * the data to the Firebase servers will also be started, and the returned - * Promise will resolve when complete. If provided, the `onComplete` callback - * will be called asynchronously after synchronization has finished. - * - * Passing `null` for the new value is equivalent to calling `remove()`; namely, - * all data at this location and all child locations will be deleted. - * - * `set()` will remove any priority stored at this location, so if priority is - * meant to be preserved, you need to use `setWithPriority()` instead. - * - * Note that modifying data with `set()` will cancel any pending transactions - * at that location, so extreme care should be taken if mixing `set()` and - * `transaction()` to modify the same data. - * - * A single `set()` will generate a single "value" event at the location where - * the `set()` was performed. - * - * @example - * var adaNameRef = firebase.database().ref('users/ada/name'); - * adaNameRef.child('first').set('Ada'); - * adaNameRef.child('last').set('Lovelace'); - * // We've written 'Ada' to the Database location storing Ada's first name, - * // and 'Lovelace' to the location storing her last name. - * - * @example - * adaNameRef.set({ first: 'Ada', last: 'Lovelace' }); - * // Exact same effect as the previous example, except we've written - * // Ada's first and last name simultaneously. - * - * @example - * adaNameRef.set({ first: 'Ada', last: 'Lovelace' }) - * .then(function() { - * console.log('Synchronization succeeded'); - * }) - * .catch(function(error) { - * console.log('Synchronization failed'); - * }); - * // Same as the previous example, except we will also log a message - * // when the data has finished synchronizing. - * - * @param {*} value The value to be written (string, number, boolean, object, - * array, or null). - * @param {function(?Error)=} onComplete Callback called when write to server is - * complete. - * @return {!firebase.Promise} Resolves when write to server is complete. - */ -firebase.database.Reference.prototype.set = function (value, onComplete) {}; - -/** - * Writes multiple values to the Database at once. - * - * The `values` argument contains multiple property-value pairs that will be - * written to the Database together. Each child property can either be a simple - * property (for example, "name") or a relative path (for example, - * "name/first") from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update - * only the referenced properties at the current location (instead of replacing - * all the child properties at the current location). - * - * The effect of the write will be visible immediately, and the corresponding - * events ('value', 'child_added', etc.) will be triggered. Synchronization of - * the data to the Firebase servers will also be started, and the returned - * Promise will resolve when complete. If provided, the `onComplete` callback - * will be called asynchronously after synchronization has finished. - * - * A single `update()` will generate a single "value" event at the location - * where the `update()` was performed, regardless of how many children were - * modified. - * - * Note that modifying data with `update()` will cancel any pending - * transactions at that location, so extreme care should be taken if mixing - * `update()` and `transaction()` to modify the same data. - * - * Passing `null` to `update()` will remove the data at this location. - * - * See - * {@link - * https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html - * Introducing multi-location updates and more}. - * - * @example - * var adaNameRef = firebase.database().ref('users/ada/name'); - * // Modify the 'first' and 'last' properties, but leave other data at - * // adaNameRef unchanged. - * adaNameRef.update({ first: 'Ada', last: 'Lovelace' }); - * - * @param {!Object} values Object containing multiple values. - * @param {function(?Error)=} onComplete Callback called when write to server is - * complete. - * @return {!firebase.Promise} Resolves when update on server is complete. - */ -firebase.database.Reference.prototype.update = function (values, onComplete) {}; - -/** - * Writes data the Database location. Like `set()` but also specifies the - * priority for that data. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - * - * @param {*} newVal - * @param {string|number|null} newPriority - * @param {function(?Error)=} onComplete - * @return {!firebase.Promise} - */ -firebase.database.Reference.prototype.setWithPriority = function ( - newVal, - newPriority, - onComplete -) {}; - -/** - * Removes the data at this Database location. - * - * Any data at child locations will also be deleted. - * - * The effect of the remove will be visible immediately and the corresponding - * event 'value' will be triggered. Synchronization of the remove to the - * Firebase servers will also be started, and the returned Promise will resolve - * when complete. If provided, the onComplete callback will be called - * asynchronously after synchronization has finished. - * - * @example - * var adaRef = firebase.database().ref('users/ada'); - * adaRef.remove() - * .then(function() { - * console.log("Remove succeeded.") - * }) - * .catch(function(error) { - * console.log("Remove failed: " + error.message) - * }); - * - * @param {function(?Error)=} onComplete Callback called when write to server is - * complete. - * @return {!firebase.Promise} Resolves when remove on server is complete. - */ -firebase.database.Reference.prototype.remove = function (onComplete) {}; - -/** - * Atomically modifies the data at this location. - * - * Atomically modify the data at this location. Unlike a normal `set()`, which - * just overwrites the data regardless of its previous value, `transaction()` is - * used to modify the existing value to a new value, ensuring there are no - * conflicts with other clients writing to the same location at the same time. - * - * To accomplish this, you pass `transaction()` an update function which is used - * to transform the current value into a new value. If another client writes to - * the location before your new value is successfully written, your update - * function will be called again with the new current value, and the write will - * be retried. This will happen repeatedly until your write succeeds without - * conflict or you abort the transaction by not returning a value from your - * update function. - * - * Note: Modifying data with `set()` will cancel any pending transactions at - * that location, so extreme care should be taken if mixing `set()` and - * `transaction()` to update the same data. - * - * Note: When using transactions with Security and Firebase Rules in place, be - * aware that a client needs `.read` access in addition to `.write` access in - * order to perform a transaction. This is because the client-side nature of - * transactions requires the client to read the data in order to transactionally - * update it. - * - * @example - * // Increment Ada's rank by 1. - * var adaRankRef = firebase.database().ref('users/ada/rank'); - * adaRankRef.transaction(function(currentRank) { - * // If users/ada/rank has never been set, currentRank will be `null`. - * return currentRank + 1; - * }); - * - * @example - * // Try to create a user for ada, but only if the user id 'ada' isn't - * // already taken - * var adaRef = firebase.database().ref('users/ada'); - * adaRef.transaction(function(currentData) { - * if (currentData === null) { - * return { name: { first: 'Ada', last: 'Lovelace' } }; - * } else { - * console.log('User ada already exists.'); - * return; // Abort the transaction. - * } - * }, function(error, committed, snapshot) { - * if (error) { - * console.log('Transaction failed abnormally!', error); - * } else if (!committed) { - * console.log('We aborted the transaction (because ada already exists).'); - * } else { - * console.log('User ada added!'); - * } - * console.log("Ada's data: ", snapshot.val()); - * }); - * - * - * @param {function(*): *} transactionUpdate A developer-supplied function which - * will be passed the current data stored at this location (as a JavaScript - * object). The function should return the new value it would like written (as - * a JavaScript object). If `undefined` is returned (i.e. you return with no - * arguments) the transaction will be aborted and the data at this location - * will not be modified. - * @param {(function(?Error, boolean, - * ?firebase.database.DataSnapshot))=} onComplete A callback - * function that will be called when the transaction completes. The callback - * is passed three arguments: a possibly-null `Error`, a `boolean` indicating - * whether the transaction was committed, and a `DataSnapshot` indicating the - * final result. If the transaction failed abnormally, the first argument will - * be an `Error` object indicating the failure cause. If the transaction - * finished normally, but no data was committed because no data was returned - * from `transactionUpdate`, then second argument will be false. If the - * transaction completed and committed data to Firebase, the second argument - * will be true. Regardless, the third argument will be a `DataSnapshot` - * containing the resulting data in this location. - * @param {boolean=} applyLocally By default, events are raised each time the - * transaction update function runs. So if it is run multiple times, you may - * see intermediate states. You can set this to false to suppress these - * intermediate states and instead wait until the transaction has completed - * before events are raised. - * @return {!firebase.Promise<{ - * committed: boolean, - * snapshot: ?firebase.database.DataSnapshot - * }>} Returns a Promise that can optionally be used instead of the onComplete - * callback to handle success and failure. - */ -firebase.database.Reference.prototype.transaction = function ( - transactionUpdate, - onComplete, - applyLocally -) {}; - -/** - * Sets a priority for the data at this Database location. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - * - * @param {string|number|null} priority - * @param {function(?Error)} onComplete - * @return {!firebase.Promise} - */ -firebase.database.Reference.prototype.setPriority = function ( - priority, - onComplete -) {}; - -/** - * @interface - * @extends {firebase.database.Reference} - * @extends {firebase.Thenable} - */ -firebase.database.ThenableReference = function () {}; - -/** - * Generates a new child location using a unique key and returns its - * `Reference`. - * - * This is the most common pattern for adding data to a collection of items. - * - * If you provide a value to `push()`, the value will be written to the - * generated location. If you don't pass a value, nothing will be written to the - * Database and the child will remain empty (but you can use the `Reference` - * elsewhere). - * - * The unique key generated by `push()` are ordered by the current time, so the - * resulting list of items will be chronologically sorted. The keys are also - * designed to be unguessable (they contain 72 random bits of entropy). - * - * - * See - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#append_to_a_list_of_data - * Append to a list of data} - *
See - * {@link - * https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html - * The 2^120 Ways to Ensure Unique Identifiers} - * - * @example - * var messageListRef = firebase.database().ref('message_list'); - * var newMessageRef = messageListRef.push(); - * newMessageRef.set({ - * 'user_id': 'ada', - * 'text': 'The Analytical Engine weaves algebraical patterns just as the Jacquard loom weaves flowers and leaves.' - * }); - * // We've appended a new message to the message_list location. - * var path = newMessageRef.toString(); - * // path will be something like - * // 'https://sample-app.firebaseio.com/message_list/-IKo28nwJLH0Nc5XeFmj' - * - * @param {*=} value Optional value to be written at the generated location. - * @param {function(?Error)=} onComplete Callback called when write to server is - * complete. - * @return {!firebase.database.ThenableReference} Combined `Promise` and - * `Reference`; resolves when write is complete, but can be used immediately - * as the `Reference` to the child location. - */ -firebase.database.Reference.prototype.push = function (value, onComplete) {}; - -/** - * Returns an `OnDisconnect` object - see - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information on how - * to use it. - * - * @return {!firebase.database.OnDisconnect} - */ -firebase.database.Reference.prototype.onDisconnect = function () {}; - -/** - * A `Query` sorts and filters the data at a Database location so only a subset - * of the child data is included. This can be used to order a collection of - * data by some attribute (for example, height of dinosaurs) as well as to - * restrict a large list of items (for example, chat messages) down to a number - * suitable for synchronizing to the client. Queries are created by chaining - * together one or more of the filter methods defined here. - * - * Just as with a `Reference`, you can receive data from a `Query` by using the - * `on()` method. You will only receive events and `DataSnapshot`s for the - * subset of the data that matches your query. - * - * Read our documentation on - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data} for more information. - * - * @interface - */ -firebase.database.Query = function () {}; - -/** - * Returns a `Reference` to the `Query`'s location. - * - * @type {!firebase.database.Reference} - */ -firebase.database.Query.prototype.ref; - -/** - * Returns whether or not the current and provided queries represent the same - * location, have the same query parameters, and are from the same instance of - * `firebase.app.App`. - * - * Two `Reference` objects are equivalent if they represent the same location - * and are from the same instance of `firebase.app.App`. - * - * Two `Query` objects are equivalent if they represent the same location, have - * the same query parameters, and are from the same instance of - * `firebase.app.App`. Equivalent queries share the same sort order, limits, and - * starting and ending points. - * - * @example - * var rootRef = firebase.database.ref(); - * var usersRef = rootRef.child("users"); - * - * usersRef.isEqual(rootRef); // false - * usersRef.isEqual(rootRef.child("users")); // true - * usersRef.parent.isEqual(rootRef); // true - * - * @example - * var rootRef = firebase.database.ref(); - * var usersRef = rootRef.child("users"); - * var usersQuery = usersRef.limitToLast(10); - * - * usersQuery.isEqual(usersRef); // false - * usersQuery.isEqual(usersRef.limitToLast(10)); // true - * usersQuery.isEqual(rootRef.limitToLast(10)); // false - * usersQuery.isEqual(usersRef.orderByKey().limitToLast(10)); // false - * - * @param {firebase.database.Query} other The query to compare against. - * @return {boolean} Whether or not the current and provided queries are - * equivalent. - */ -firebase.database.Query.prototype.isEqual = function (other) {}; - -/** - * Listens for data changes at a particular location. - * - * This is the primary way to read data from a Database. Your callback - * will be triggered for the initial data and again whenever the data changes. - * Use `off( )` to stop receiving updates. See - * {@link https://firebase.google.com/docs/database/web/retrieve-data - * Retrieve Data on the Web} - * for more details. - * - *

value event

- * - * This event will trigger once with the initial data stored at this location, - * and then trigger again each time the data changes. The `DataSnapshot` passed - * to the callback will be for the location at which `on()` was called. It - * won't trigger until the entire contents has been synchronized. If the - * location has no data, it will be triggered with an empty `DataSnapshot` - * (`val()` will return `null`). - * - *

child_added event

- * - * This event will be triggered once for each initial child at this location, - * and it will be triggered again every time a new child is added. The - * `DataSnapshot` passed into the callback will reflect the data for the - * relevant child. For ordering purposes, it is passed a second argument which - * is a string containing the key of the previous sibling child by sort order, - * or `null` if it is the first child. - * - *

child_removed event

- * - * This event will be triggered once every time a child is removed. The - * `DataSnapshot` passed into the callback will be the old data for the child - * that was removed. A child will get removed when either: - * - * - a client explicitly calls `remove()` on that child or one of its ancestors - * - a client calls `set(null)` on that child or one of its ancestors - * - that child has all of its children removed - * - there is a query in effect which now filters out the child (because it's - * sort order changed or the max limit was hit) - * - *

child_changed event

- * - * This event will be triggered when the data stored in a child (or any of its - * descendants) changes. Note that a single `child_changed` event may represent - * multiple changes to the child. The `DataSnapshot` passed to the callback will - * contain the new child contents. For ordering purposes, the callback is also - * passed a second argument which is a string containing the key of the previous - * sibling child by sort order, or `null` if it is the first child. - * - *

child_moved event

- * - * This event will be triggered when a child's sort order changes such that its - * position relative to its siblings changes. The `DataSnapshot` passed to the - * callback will be for the data of the child that has moved. It is also passed - * a second argument which is a string containing the key of the previous - * sibling child by sort order, or `null` if it is the first child. - * - * @example Handle a new value: - * ref.on('value', function(dataSnapshot) { - * ... - * }); - * - * @example Handle a new child: - * ref.on('child_added', function(childSnapshot, prevChildKey) { - * ... - * }); - * - * @example Handle child removal: - * ref.on('child_removed', function(oldChildSnapshot) { - * ... - * }); - * - * @example Handle child data changes: - * ref.on('child_changed', function(childSnapshot, prevChildKey) { - * ... - * }); - * - * @example Handle child ordering changes: - * ref.on('child_moved', function(childSnapshot, prevChildKey) { - * ... - * }); - * - * @param {string} eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." - * @param {!function(firebase.database.DataSnapshot, string=)} callback A - * callback that fires when the specified event occurs. The callback will be - * passed a DataSnapshot. For ordering purposes, "child_added", - * "child_changed", and "child_moved" will also be passed a string containing - * the key of the previous child, by sort order, or `null` if it is the - * first child. - * @param {(function(Error)|Object)=} cancelCallbackOrContext An optional - * callback that will be notified if your event subscription is ever canceled - * because your client does not have permission to read this data (or it had - * permission but has now lost it). This callback will be passed an `Error` - * object indicating why the failure occurred. - * @param {Object=} context If provided, this object will be used as `this` - * when calling your callback(s). - * @return {!function(firebase.database.DataSnapshot, string=)} The provided - * callback function is returned unmodified. This is just for convenience if - * you want to pass an inline function to `on()` but store the callback - * function for later passing to `off()`. - */ -firebase.database.Query.prototype.on = function ( - eventType, - callback, - cancelCallbackOrContext, - context -) {}; - -/** - * Detaches a callback previously attached with `on()`. - * - * Detach a callback previously attached with `on()`. Note that if `on()` was - * called multiple times with the same eventType and callback, the callback - * will be called multiple times for each event, and `off()` must be called - * multiple times to remove the callback. Calling `off()` on a parent listener - * will not automatically remove listeners registered on child nodes, `off()` - * must also be called on any child listeners to remove the callback. - * - * If a callback is not specified, all callbacks for the specified eventType - * will be removed. Similarly, if no eventType or callback is specified, all - * callbacks for the `Reference` will be removed. - * - * @example - * var onValueChange = function(dataSnapshot) { ... }; - * ref.on('value', onValueChange); - * ref.child('meta-data').on('child_added', onChildAdded); - * // Sometime later... - * ref.off('value', onValueChange); - * - * // You must also call off() for any child listeners on ref - * // to cancel those callbacks - * ref.child('meta-data').off('child_added', onValueAdded); - * - * @example - * // Or you can save a line of code by using an inline function - * // and on()'s return value. - * var onValueChange = ref.on('value', function(dataSnapshot) { ... }); - * // Sometime later... - * ref.off('value', onValueChange); - * - * @param {string=} eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." - * @param {function(!firebase.database.DataSnapshot, ?string=)=} callback The - * callback function that was passed to `on()`. - * @param {Object=} context The context that was passed to `on()`. - */ -firebase.database.Query.prototype.off = function ( - eventType, - callback, - context -) {}; - -/** - * Listens for exactly one event of the specified event type, and then stops - * listening. - * - * This is equivalent to calling {@link firebase.database.Query#on `on()`}, and - * then calling {@link firebase.database.Query#off `off()`} inside the callback - * function. See {@link firebase.database.Query#on `on()`} for details on the - * event types. - * - * @example - * // Basic usage of .once() to read the data located at ref. - * ref.once('value') - * .then(function(dataSnapshot) { - * // handle read data. - * }); - * - * @param {string} eventType One of the following strings: "value", - * "child_added", "child_changed", "child_removed", or "child_moved." - * @param {function(!firebase.database.DataSnapshot, string=)=} successCallback A - * callback that fires when the specified event occurs. The callback will be - * passed a DataSnapshot. For ordering purposes, "child_added", - * "child_changed", and "child_moved" will also be passed a string containing - * the key of the previous child by sort order, or `null` if it is the - * first child. - * @param {(function(Error)|Object)=} failureCallbackOrContext An optional - * callback that will be notified if your client does not have permission to - * read the data. This callback will be passed an `Error` object indicating - * why the failure occurred. - * @param {Object=} context If provided, this object will be used as `this` - * when calling your callback(s). - * @return {!firebase.Promise<*>} - */ -firebase.database.Query.prototype.once = function ( - eventType, - successCallback, - failureCallbackOrContext, - context -) {}; - -/** - * Generates a new `Query` limited to the first specific number of children. - * - * The `limitToFirst()` method is used to set a maximum number of children to be - * synced for a given callback. If we set a limit of 100, we will initially only - * receive up to 100 `child_added` events. If we have fewer than 100 messages - * stored in our Database, a `child_added` event will fire for each message. - * However, if we have over 100 messages, we will only receive a `child_added` - * event for the first 100 ordered messages. As items change, we will receive - * `child_removed` events for each item that drops out of the active list so - * that the total number stays at 100. - * - * You can read more about `limitToFirst()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * // Find the two shortest dinosaurs. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").limitToFirst(2).on("child_added", function(snapshot) { - * // This will be called exactly two times (unless there are less than two - * // dinosaurs in the Database). - * - * // It will also get fired again if one of the first two dinosaurs is - * // removed from the data set, as a new dinosaur will now be the second - * // shortest. - * console.log(snapshot.key); - * }); - * - * @param {number} limit The maximum number of nodes to include in this query. - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.limitToFirst = function (limit) {}; - -/** - * Generates a new `Query` object limited to the last specific number of - * children. - * - * The `limitToLast()` method is used to set a maximum number of children to be - * synced for a given callback. If we set a limit of 100, we will initially only - * receive up to 100 `child_added` events. If we have fewer than 100 messages - * stored in our Database, a `child_added` event will fire for each message. - * However, if we have over 100 messages, we will only receive a `child_added` - * event for the last 100 ordered messages. As items change, we will receive - * `child_removed` events for each item that drops out of the active list so - * that the total number stays at 100. - * - * You can read more about `limitToLast()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * // Find the two heaviest dinosaurs. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("weight").limitToLast(2).on("child_added", function(snapshot) { - * // This callback will be triggered exactly two times, unless there are - * // fewer than two dinosaurs stored in the Database. It will also get fired - * // for every new, heavier dinosaur that gets added to the data set. - * console.log(snapshot.key); - * }); - * - * @param {number} limit The maximum number of nodes to include in this query. - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.limitToLast = function (limit) {}; - -/** - * Generates a new `Query` object ordered by the specified child key. - * - * Queries can only order by one key at a time. Calling `orderByChild()` - * multiple times on the same query is an error. - * - * Firebase queries allow you to order your data by any child key on the fly. - * However, if you know in advance what your indexes will be, you can define - * them via the .indexOn rule in your Security Rules for better performance. See - * the {@link https://firebase.google.com/docs/database/security/indexing-data - * .indexOn} rule for more information. - * - * You can read more about `orderByChild()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").on("child_added", function(snapshot) { - * console.log(snapshot.key + " was " + snapshot.val().height + " m tall"); - * }); - * - * @param {string} path - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.orderByChild = function (path) {}; - -/** - * Generates a new `Query` object ordered by key. - * - * Sorts the results of a query by their (ascending) key values. - * - * You can read more about `orderByKey()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByKey().on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.orderByKey = function () {}; - -/** - * Generates a new `Query` object ordered by priority. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data} for alternatives to priority. - * - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.orderByPriority = function () {}; - -/** - * Generates a new `Query` object ordered by value. - * - * If the children of a query are all scalar values (string, number, or - * boolean), you can order the results by their (ascending) values. - * - * You can read more about `orderByValue()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sort_data - * Sort data}. - * - * @example - * var scoresRef = firebase.database().ref("scores"); - * scoresRef.orderByValue().limitToLast(3).on("value", function(snapshot) { - * snapshot.forEach(function(data) { - * console.log("The " + data.key + " score is " + data.val()); - * }); - * }); - * - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.orderByValue = function () {}; - -/** - * Creates a `Query` with the specified starting point. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary - * starting and ending points for your queries. - * - * The starting point is inclusive, so children with exactly the specified value - * will be included in the query. The optional key argument can be used to - * further limit the range of the query. If it is specified, then children that - * have exactly the specified value must also have a key name greater than or - * equal to the specified key. - * - * You can read more about `startAt()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * // Find all dinosaurs that are at least three meters tall. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").startAt(3).on("child_added", function(snapshot) { - * console.log(snapshot.key) - * }); - * - * @param {number|string|boolean|null} value The value to start at. The argument - * type depends on which `orderBy*()` function was used in this query. - * Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param {string=} key The child key to start at. This argument is only allowed - * if ordering by child, value, or priority. - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.startAt = function (value, key) {}; - -/** - * Creates a `Query` with the specified ending point. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows you to choose arbitrary - * starting and ending points for your queries. - * - * The ending point is inclusive, so children with exactly the specified value - * will be included in the query. The optional key argument can be used to - * further limit the range of the query. If it is specified, then children that - * have exactly the specified value must also have a key name less than or equal - * to the specified key. - * - * You can read more about `endAt()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * // Find all dinosaurs whose names come before Pterodactyl lexicographically. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByKey().endAt("pterodactyl").on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * - * @param {number|string|boolean|null} value The value to end at. The argument - * type depends on which `orderBy*()` function was used in this query. - * Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param {string=} key The child key to end at, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * child, value, or priority. - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.endAt = function (value, key) {}; - -/** - * Creates a `Query` that includes children that match the specified value. - * - * Using `startAt()`, `endAt()`, and `equalTo()` allows us to choose arbitrary - * starting and ending points for our queries. - * - * The optional key argument can be used to further limit the range of the - * query. If it is specified, then children that have exactly the specified - * value must also have exactly the specified key as their key name. This can be - * used to filter result sets with many matches for the same value. - * - * You can read more about `equalTo()` in - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#filtering_data - * Filtering data}. - * - * @example - * // Find all dinosaurs whose height is exactly 25 meters. - * var ref = firebase.database().ref("dinosaurs"); - * ref.orderByChild("height").equalTo(25).on("child_added", function(snapshot) { - * console.log(snapshot.key); - * }); - * - * @param {number|string|boolean|null} value The value to match for. The - * argument type depends on which `orderBy*()` function was used in this - * query. Specify a value that matches the `orderBy*()` type. When used in - * combination with `orderByKey()`, the value must be a string. - * @param {string=} key The child key to start at, among the children with the - * previously specified priority. This argument is only allowed if ordering by - * child, value, or priority. - * @return {!firebase.database.Query} - */ -firebase.database.Query.prototype.equalTo = function (value, key) {}; - -/** - * Gets the absolute URL for this location. - * - * The `toString()` method returns a URL that is ready to be put into a browser, - * curl command, or a `firebase.database().refFromURL()` call. Since all of - * those expect the URL to be url-encoded, `toString()` returns an encoded URL. - * - * Append '.json' to the returned URL when typed into a browser to download - * JSON-formatted data. If the location is secured (that is, not publicly - * readable), you will get a permission-denied error. - * - * @example - * // Calling toString() on a root Firebase reference returns the URL where its - * // data is stored within the Database: - * var rootRef = firebase.database().ref(); - * var rootUrl = rootRef.toString(); - * // rootUrl === "https://sample-app.firebaseio.com/". - * - * // Calling toString() at a deeper Firebase reference returns the URL of that - * // deep path within the Database: - * var adaRef = rootRef.child('users/ada'); - * var adaURL = adaRef.toString(); - * // adaURL === "https://sample-app.firebaseio.com/users/ada". - * - * @return {string} The absolute URL for this location. - * @override - */ -firebase.database.Query.prototype.toString = function () {}; - -/** - * Returns a JSON-serializable representation of this object. - * - * @return {!Object} A JSON-serializable representation of this object. - */ -firebase.database.Query.prototype.toJSON = function () {}; - -/** - * A `DataSnapshot` contains data from a Database location. - * - * Any time you read data from the Database, you receive the data as a - * `DataSnapshot`. A `DataSnapshot` is passed to the event callbacks you attach - * with `on()` or `once()`. You can extract the contents of the snapshot as a - * JavaScript object by calling the `val()` method. Alternatively, you can - * traverse into the snapshot by calling `child()` to return child snapshots - * (which you could then call `val()` on). - * - * A `DataSnapshot` is an efficiently generated, immutable copy of the data at - * a Database location. It cannot be modified and will never change (to modify - * data, you always call the `set()` method on a `Reference` directly). - * - * @interface - */ -firebase.database.DataSnapshot = function () {}; - -/** - * Extracts a JavaScript value from a `DataSnapshot`. - * - * Depending on the data in a `DataSnapshot`, the `val()` method may return a - * scalar type (string, number, or boolean), an array, or an object. It may also - * return null, indicating that the `DataSnapshot` is empty (contains no data). - * - * @example - * // Write and then read back a string from the Database. - * ref.set("hello") - * .then(function() { - * return ref.once("value"); - * }) - * .then(function(snapshot) { - * var data = snapshot.val(); // data === "hello" - * }); - * - * @example - * // Write and then read back a JavaScript object from the Database. - * ref.set({ name: "Ada", age: 36 }) - * .then(function() { - * return ref.once("value"); - * }) - * .then(function(snapshot) { - * var data = snapshot.val(); - * // data is { "name": "Ada", "age": 36 } - * // data.name === "Ada" - * // data.age === 36 - * }); - * - * @return {*} The DataSnapshot's contents as a JavaScript value (Object, - * Array, string, number, boolean, or `null`). - */ -firebase.database.DataSnapshot.prototype.val = function () {}; - -/** - * Exports the entire contents of the DataSnapshot as a JavaScript object. - * - * The `exportVal()` method is similar to `val()`, except priority information - * is included (if available), making it suitable for backing up your data. - * - * @return {*} The DataSnapshot's contents as a JavaScript value (Object, - * Array, string, number, boolean, or `null`). - */ -firebase.database.DataSnapshot.prototype.exportVal = function () {}; - -/** - * Returns true if this `DataSnapshot` contains any data. It is slightly more - * efficient than using `snapshot.val() !== null`. - * - * @example - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Test for the existence of certain keys within a DataSnapshot - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.exists(); // true - * var b = snapshot.child("name").exists(); // true - * var c = snapshot.child("name/first").exists(); // true - * var d = snapshot.child("name/middle").exists(); // false - * }); - * - * @return {boolean} - */ -firebase.database.DataSnapshot.prototype.exists = function () {}; - -/** - * Gets another `DataSnapshot` for the location at the specified relative path. - * - * Passing a relative path to the `child()` method of a DataSnapshot returns - * another `DataSnapshot` for the location at the specified relative path. The - * relative path can either be a simple child name (for example, "ada") or a - * deeper, slash-separated path (for example, "ada/name/first"). If the child - * location has no data, an empty `DataSnapshot` (that is, a `DataSnapshot` - * whose value is `null`) is returned. - * - * @example - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Test for the existence of certain keys within a DataSnapshot - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var name = snapshot.child("name").val(); // {first:"Ada",last:"Lovelace"} - * var firstName = snapshot.child("name/first").val(); // "Ada" - * var lastName = snapshot.child("name").child("last").val(); // "Lovelace" - * var age = snapshot.child("age").val(); // null - * }); - * - * @param {string} path A relative path to the location of child data. - * @return {!firebase.database.DataSnapshot} - */ -firebase.database.DataSnapshot.prototype.child = function (path) {}; - -/** - * Returns true if the specified child path has (non-null) data. - * - * @example - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * // Determine which child keys in DataSnapshot have data. - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var hasName = snapshot.hasChild("name"); // true - * var hasAge = snapshot.hasChild("age"); // false - * }); - * - * @param {string} path A relative path to the location of a potential child. - * @return {boolean} `true` if data exists at the specified child path; else - * `false`. - */ -firebase.database.DataSnapshot.prototype.hasChild = function (path) {}; - -/** - * Gets the priority value of the data in this `DataSnapshot`. - * - * Applications need not use priority but can order collections by - * ordinary properties (see - * {@link - * https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data - * Sorting and filtering data}). - * - * @return {string|number|null} - */ -firebase.database.DataSnapshot.prototype.getPriority = function () {}; - -/** - * Enumerates the top-level children in the `DataSnapshot`. - * - * Because of the way JavaScript objects work, the ordering of data in the - * JavaScript object returned by `val()` is not guaranteed to match the ordering - * on the server nor the ordering of `child_added` events. That is where - * `forEach()` comes in handy. It guarantees the children of a `DataSnapshot` - * will be iterated in their query order. - * - * If no explicit `orderBy*()` method is used, results are returned - * ordered by key (unless priorities are used, in which case, results are - * returned by priority). - * - * @example - * - * // Assume we have the following data in the Database: - * { - * "users": { - * "ada": { - * "first": "Ada", - * "last": "Lovelace" - * }, - * "alan": { - * "first": "Alan", - * "last": "Turing" - * } - * } - * } - * - * // Loop through users in order with the forEach() method. The callback - * // provided to forEach() will be called synchronously with a DataSnapshot - * // for each child: - * var query = firebase.database().ref("users").orderByKey(); - * query.once("value") - * .then(function(snapshot) { - * snapshot.forEach(function(childSnapshot) { - * // key will be "ada" the first time and "alan" the second time - * var key = childSnapshot.key; - * // childData will be the actual contents of the child - * var childData = childSnapshot.val(); - * }); - * }); - * - * @example - * // You can cancel the enumeration at any point by having your callback - * // function return true. For example, the following code sample will only - * // fire the callback function one time: - * var query = firebase.database().ref("users").orderByKey(); - * query.once("value") - * .then(function(snapshot) { - * snapshot.forEach(function(childSnapshot) { - * var key = childSnapshot.key; // "ada" - * - * // Cancel enumeration - * return true; - * }); - * }); - * - * @param {function(!firebase.database.DataSnapshot): boolean} action A function - * that will be called for each child DataSnapshot. The callback can return - * true to cancel further enumeration. - * @return {boolean} true if enumeration was canceled due to your callback - * returning true. - */ -firebase.database.DataSnapshot.prototype.forEach = function (action) {}; - -/** - * Returns whether or not the `DataSnapshot` has any non-`null` child - * properties. - * - * You can use `hasChildren()` to determine if a `DataSnapshot` has any - * children. If it does, you can enumerate them using `forEach()`. If it - * doesn't, then either this snapshot contains a primitive value (which can be - * retrieved with `val()`) or it is empty (in which case, `val()` will return - * `null`). - * - * @example - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.hasChildren(); // true - * var b = snapshot.child("name").hasChildren(); // true - * var c = snapshot.child("name/first").hasChildren(); // false - * }); - * - * @return {boolean} true if this snapshot has any children; else false. - */ -firebase.database.DataSnapshot.prototype.hasChildren = function () {}; - -/** - * The key (last part of the path) of the location of this `DataSnapshot`. - * - * The last token in a Database location is considered its key. For example, - * "ada" is the key for the /users/ada/ node. Accessing the key on any - * `DataSnapshot` will return the key for the location that generated it. - * However, accessing the key on the root URL of a Database will return `null`. - * - * @example - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var key = snapshot.key; // "ada" - * var childKey = snapshot.child("name/last").key; // "last" - * }); - * - * @example - * var rootRef = firebase.database().ref(); - * rootRef.once("value") - * .then(function(snapshot) { - * var key = snapshot.key; // null - * var childKey = snapshot.child("users/ada").key; // "ada" - * }); - * - * @type {string|null} - */ -firebase.database.DataSnapshot.prototype.key; - -/** - * Returns the number of child properties of this `DataSnapshot`. - * - * @example - * // Assume we have the following data in the Database: - * { - * "name": { - * "first": "Ada", - * "last": "Lovelace" - * } - * } - * - * var ref = firebase.database().ref("users/ada"); - * ref.once("value") - * .then(function(snapshot) { - * var a = snapshot.numChildren(); // 1 ("name") - * var b = snapshot.child("name").numChildren(); // 2 ("first", "last") - * var c = snapshot.child("name/first").numChildren(); // 0 - * }); - * - * @return {number} - */ -firebase.database.DataSnapshot.prototype.numChildren = function () {}; - -/** - * The `Reference` for the location that generated this `DataSnapshot`. - * - * @type {!firebase.database.Reference} - */ -firebase.database.DataSnapshot.prototype.ref; - -/** - * Returns a JSON-serializable representation of this object. - * - * @return {?Object} A JSON-serializable representation of this object. - */ -firebase.database.DataSnapshot.prototype.toJSON = function () {}; - -/** - * The `onDisconnect` class allows you to write or clear data when your client - * disconnects from the Database server. These updates occur whether your - * client disconnects cleanly or not, so you can rely on them to clean up data - * even if a connection is dropped or a client crashes. - * - * The `onDisconnect` class is most commonly used to manage presence in - * applications where it is useful to detect how many clients are connected and - * when other clients disconnect. See - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information. - * - * To avoid problems when a connection is dropped before the requests can be - * transferred to the Database server, these functions should be called before - * writing any data. - * - * Note that `onDisconnect` operations are only triggered once. If you want an - * operation to occur each time a disconnect occurs, you'll need to re-establish - * the `onDisconnect` operations each time you reconnect. - * - * @interface - */ -firebase.database.OnDisconnect = function () {}; - -/** - * Cancels all previously queued `onDisconnect()` set or update events for this - * location and all children. - * - * If a write has been queued for this location via a `set()` or `update()` at a - * parent location, the write at this location will be canceled, though writes - * to sibling locations will still occur. - * - * @example - * var ref = firebase.database().ref("onlineState"); - * ref.onDisconnect().set(false); - * // ... sometime later - * ref.onDisconnect().cancel(); - * - * @param {function(?Error)=} onComplete An optional callback function that will - * be called when synchronization to the server has completed. The callback - * will be passed a single parameter: null for success, or an Error object - * indicating a failure. - * @return {!firebase.Promise} Resolves when synchronization to the server - * is complete. - */ -firebase.database.OnDisconnect.prototype.cancel = function (onComplete) {}; - -/** - * Ensures the data at this location is deleted when the client is disconnected - * (due to closing the browser, navigating to a new page, or network issues). - * - * @param {function(?Error)=} onComplete An optional callback function that will - * be called when synchronization to the server has completed. The callback - * will be passed a single parameter: null for success, or an Error object - * indicating a failure. - * @return {!firebase.Promise} Resolves when synchronization to the server - * is complete. - */ -firebase.database.OnDisconnect.prototype.remove = function (onComplete) {}; - -/** - * Ensures the data at this location is set to the specified value when the - * client is disconnected (due to closing the browser, navigating to a new page, - * or network issues). - * - * `set()` is especially useful for implementing "presence" systems, where a - * value should be changed or cleared when a user disconnects so that they - * appear "offline" to other users. See - * {@link - * https://firebase.google.com/docs/database/web/offline-capabilities - * Enabling Offline Capabilities in JavaScript} for more information. - * - * Note that `onDisconnect` operations are only triggered once. If you want an - * operation to occur each time a disconnect occurs, you'll need to re-establish - * the `onDisconnect` operations each time. - * - * @example - * var ref = firebase.database().ref("users/ada/status"); - * ref.onDisconnect().set("I disconnected!"); - * - * @param {*} value The value to be written to this location on - * disconnect (can be an object, array, string, number, boolean, or null). - * @param {function(?Error)=} onComplete An optional callback function that - * will be called when synchronization to the Database server has completed. - * The callback will be passed a single parameter: null for success, or an - * `Error` object indicating a failure. - * @return {!firebase.Promise} Resolves when synchronization to the - * Database is complete. - */ -firebase.database.OnDisconnect.prototype.set = function (value, onComplete) {}; - -/** - * Ensures the data at this location is set to the specified value and priority - * when the client is disconnected (due to closing the browser, navigating to a - * new page, or network issues). - * - * @param {*} value - * @param {number|string|null} priority - * @param {function(?Error)=} onComplete - * @return {!firebase.Promise} - */ -firebase.database.OnDisconnect.prototype.setWithPriority = function ( - value, - priority, - onComplete -) {}; - -/** - * Writes multiple values at this location when the client is disconnected (due - * to closing the browser, navigating to a new page, or network issues). - * - * The `values` argument contains multiple property-value pairs that will be - * written to the Database together. Each child property can either be a simple - * property (for example, "name") or a relative path (for example, "name/first") - * from the current location to the data to update. - * - * As opposed to the `set()` method, `update()` can be use to selectively update - * only the referenced properties at the current location (instead of replacing - * all the child properties at the current location). - * - * See more examples using the connected version of - * {@link firebase.database.Reference#update `update()`}. - * - * @example - * var ref = firebase.database().ref("users/ada"); - * ref.update({ - * onlineState: true, - * status: "I'm online." - * }); - * ref.onDisconnect().update({ - * onlineState: false, - * status: "I'm offline." - * }); - * - * @param {!Object} values Object containing multiple values. - * @param {function(?Error)=} onComplete An optional callback function that will - * be called when synchronization to the server has completed. The - * callback will be passed a single parameter: null for success, or an Error - * object indicating a failure. - * @return {!firebase.Promise} Resolves when synchronization to the - * Database is complete. - */ -firebase.database.OnDisconnect.prototype.update = function ( - values, - onComplete -) {}; diff --git a/packages/firebase/externs/firebase-database-internal-externs.js b/packages/firebase/externs/firebase-database-internal-externs.js deleted file mode 100644 index 54f274d53a9..00000000000 --- a/packages/firebase/externs/firebase-database-internal-externs.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * The INTERNAL interfaces to the Firebase Database service. - * Version: ${JSCORE_VERSION} - * - * @externs - */ - -/** @return {!firebase.Promise} */ -firebase.database.Database.prototype.INTERNAL.delete = function () {}; - -/** @const {!Object} */ -firebase.database.INTERNAL; diff --git a/packages/firebase/externs/firebase-error-externs.js b/packages/firebase/externs/firebase-error-externs.js deleted file mode 100644 index 5a7989a5c8e..00000000000 --- a/packages/firebase/externs/firebase-error-externs.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase Error API. - * @externs - */ - -//--------------------------// -// firebase.FirebaseError // -//--------------------------// - -/** - * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In - * addition to a message string and stack trace, it contains a string code. - * - * @interface - */ -firebase.FirebaseError; - -/** - * Error codes are strings using the following format: `"service/string-code"`. - * Some examples include `"app/no-app"` and `"auth/user-not-found"`. - * - * While the message for a given error can change, the code will remain the same - * between backward-compatible versions of the Firebase SDK. - * - * @type {string} - */ -firebase.FirebaseError.prototype.code; - -/** - * An explanatory message for the error that just occurred. - * - * This message is designed to be helpful to you, the developer. It is not - * intended to be displayed to the end user of your application (as it will - * generally not convey meaningful information to them). - * - * @type {string} - */ -firebase.FirebaseError.prototype.message; - -/** - * The name of the class of errors, namely `"FirebaseError"`. - * - * @type {string} - */ -firebase.FirebaseError.prototype.name; - -/** - * A string value containing the execution backtrace when the error originally - * occurred. This may not always be available. - * - * This information can be useful to you and can be sent to - * {@link https://firebase.google.com/support/ Firebase Support} to help - * explain the cause of an error. - * - * @type {string|undefined} - */ -firebase.FirebaseError.prototype.stack; diff --git a/packages/firebase/externs/firebase-externs.js b/packages/firebase/externs/firebase-externs.js deleted file mode 100644 index 27133c56d4b..00000000000 --- a/packages/firebase/externs/firebase-externs.js +++ /dev/null @@ -1,57 +0,0 @@ -/** @externs */ - -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// Node externs -var process; -/** - * @constructor - * @param {!string} a - * @param {!string} b - */ -var Buffer = function (a, b) {}; -/** - * @param {string=} encoding - * @return {!string} - */ -Buffer.prototype.toString = function (encoding) { - return 'dummy'; -}; - -// Browser externs -var MozWebSocket; - -// WinRT -var Windows; - -// Long-polling -var jsonpCB; - -// -// CommonJS externs -// - -/** @const */ -var module = {}; - -/** @type {*} */ -module.exports = {}; - -/** - * @param {string} moduleName - * @return {*} - */ -var require = function (moduleName) {}; diff --git a/packages/firebase/externs/firebase-firestore-externs.js b/packages/firebase/externs/firebase-firestore-externs.js deleted file mode 100644 index 0a653c4e082..00000000000 --- a/packages/firebase/externs/firebase-firestore-externs.js +++ /dev/null @@ -1,1383 +0,0 @@ -/** - * @license Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Cloud Firestore API. - * @externs - */ - -/** - * @namespace firebase.firestore - * @param {!firebase.app.App=} app - * - * @return {!firebase.firestore.Firestore} Firestore - */ -firebase.firestore = function (app) {}; - -/** - * The Cloud Firestore service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.firestore `firebase.firestore()`}. - * - * @interface - */ -firebase.firestore.Firestore = function () {}; - -/** - * Specifies custom configurations for your Cloud Firestore instance. - * You must set these before invoking any other methods. - * - * @interface - */ -firebase.firestore.Settings = function () {}; - -/** - * Enables the use of `Timestamps` for timestamp fields in `DocumentSnapshots`. - * - * Currently, Firestore returns timestamp fields as `Date` but `Date` only - * supports millisecond precision, which leads to truncation and causes - * unexpected behavior when using a timestamp from a snapshot as a part - * of a subsequent query. - * - * Setting `timestampsInSnapshots` to `true` will cause Firestore to return - * `Timestamp` values instead of `Date`, which avoids truncation. Note that you - * must also change any code that uses `Date` to use `Timestamp` instead. - * - * WARNING: In the future, `timestampsInSnapshots: true` will become the - * default and this option will be removed. You should change your code to - * use `Timestamp` and opt-in to this new behavior as soon as you can. - * - * @type {boolean} - */ -firebase.firestore.Settings.prototype.timestampsInSnapshots; - -/** - * Sets the verbosity of Cloud Firestore logs (debug, error, or silent). - * - * @param {string} logLevel - * The verbosity you set for activity and error logging. Can be any of - * the following values: - * - *
    - *
  • debug for the most verbose logging level, primarily for - * dubugging.
  • - *
  • error to log errors only.
  • - *
  • silent to turn off logging.
  • - *
- */ -firebase.firestore.Firestore.prototype.setLogLevel = function (logLevel) {}; - -/** - * Specifies custom settings to be used to configure the `Firestore` - * instance. Must be set before invoking any other methods. - * - * @param {!firebase.firestore.Settings} settings - * The settings for your Cloud Firestore instance. - */ -firebase.firestore.Firestore.prototype.settings = function (settings) {}; - -/** - * Attempts to enable persistent storage, if possible. - * - * Must be called before any other methods (other than settings()). - * - * If this fails, enablePersistence() will reject the promise it returns. - * Note that even after this failure, the firestore instance will remain - * usable, however offline persistence will be disabled. - * - * There are several reasons why this can fail, which can be identified by - * the `code` on the error. - * - * * failed-precondition: The app is already open in another browser tab. - * * unimplemented: The browser is incompatible with the offline - * persistence implementation. - * - * @return {!Promise} A promise that represents - * successfully enabling persistent storage. - */ -firebase.firestore.Firestore.prototype.enablePersistence = function () {}; - -/** - * Re-enables use of the network for this Firestore instance after a prior - * call to {@link firebase.firestore.Firestore#disableNetwork - * `disableNetwork()`}. - * - * @return {!Promise} A promise that is resolved once the network has been - * enabled. - */ -firebase.firestore.Firestore.prototype.enableNetwork = function () {}; - -/** - * Disables network usage for this instance. It can be re-enabled via - * {@link firebase.firestore.Firestore#enableNetwork `enableNetwork()`}. While - * the network is disabled, any snapshot listeners or get() calls will return - * results from cache, and any write operations will be queued until the network - * is restored. - * - * @return {!Promise} A promise that is resolved once the network has been - * disabled. - */ -firebase.firestore.Firestore.prototype.disableNetwork = function () {}; - -/** - * Gets a `CollectionReference` instance that refers to the collection at - * the specified path. - * - * @param {string} collectionPath - * A slash-separated path to a collection. - * - * @return {!firebase.firestore.CollectionReference} - * The `CollectionReference` instance. - */ -firebase.firestore.Firestore.prototype.collection = function ( - collectionPath -) {}; - -/** - * Gets a `DocumentReference` instance that refers to the document at the - * specified path. - * - * @param {string} documentPath - * A slash-separated path to a document. - * - * @return {!firebase.firestore.DocumentReference} - * The `DocumentReference` instance. - */ -firebase.firestore.Firestore.prototype.doc = function (documentPath) {}; - -/** - * Executes the given `updateFunction` and then attempts to commit the changes - * applied within the transaction. If any document read within the transaction - * has changed, Cloud Firestore retries the `updateFunction`. If it fails to - * commit after 5 attempts, the transaction fails. - * - * @param {function(!firebase.firestore.Transaction)} updateFunction - * The function to execute within the transaction context. - * - * @return {!Promise} - * If the transaction completed successfully or was explicitly aborted - * (the `updateFunction` returned a failed promise), - * the promise returned by the updateFunction is returned here. Else, if the - * transaction failed, a rejected promise with the corresponding failure - * error will be returned. - */ -firebase.firestore.Firestore.prototype.runTransaction = function ( - updateFunction -) {}; - -/** - * Creates a write batch, used for performing multiple writes as a single - * atomic operation. - * - * @return {!firebase.firestore.WriteBatch} - * A `WriteBatch` that can be used to atomically execute multiple writes. - */ -firebase.firestore.Firestore.prototype.batch = function () {}; - -/** - * The {@link firebase.app.App app} associated with this `Firestore` service - * instance. - * - * @type {!firebase.app.App} - */ -firebase.firestore.Firestore.prototype.app; - -/** - * An immutable object representing a geo point in Cloud Firestore. The geo - * point is represented as latitude/longitude pair. - * @constructor - * - * @param {number} latitude - * Latitude values are in the range of -90 to 90. - * - * @param {number} longitude - * Longitude values are in the range of -180 to 180. - */ -firebase.firestore.GeoPoint = function (latitude, longitude) {}; - -/** - * The latitude of this GeoPoint instance. - * - * @type {number} - */ -firebase.firestore.GeoPoint.prototype.latitude; - -/** - * The longitude of this GeoPoint instance. - * - * @type {number} - */ -firebase.firestore.GeoPoint.prototype.longitude; - -/** - * Returns 'true' if this `GeoPoint` is equal to the provided one. - * - * @param {!firebase.firestore.GeoPoint} other - * The `GeoPoint` to compare against. - * - * @return {boolean} 'true' if this `GeoPoint` is equal to the provided one. - */ -firebase.firestore.GeoPoint.prototype.isEqual = function (other) {}; - -/** - * An immutable object representing an array of bytes. - * @interface - */ -firebase.firestore.Blob = function () {}; - -/** - * Returns 'true' if this `Blob` is equal to the provided one. - * - * @param {!firebase.firestore.Blob} other - * The `Blob` to compare against. - * - * @return {boolean} 'true' if this `Blob` is equal to the provided one. - */ -firebase.firestore.Blob.prototype.isEqual = function (other) {}; - -/** - * Creates a new Blob from the given Base64 string, converting it to bytes. - * - * @param {string} base64 - * The Base64 string used to create the Blob object. - * - * @return {!firebase.firestore.Blob} - * The Blob created from the Base64 string. - */ -firebase.firestore.Blob.fromBase64String = function (base64) {}; - -/** - * Creates a new Blob from the given Uint8Array. - * - * @param {!Uint8Array} array - * The Uint8Array used to create the Blob object. - * - * @return {!firebase.firestore.Blob} - * The Blob created from the Uint8Array. - */ -firebase.firestore.Blob.fromUint8Array = function (array) {}; - -/** - * Returns the bytes of a Blob as a Base64-encoded string. - * - * @return {string} - * The Base64-encoded string created from the Blob object. - */ -firebase.firestore.Blob.prototype.toBase64 = function () {}; - -/** - * Returns the bytes of a Blob in a new Uint8Array. - * - * @return {!Uint8Array} - * The Uint8Array created from the Blob object. - */ -firebase.firestore.Blob.prototype.toUint8Array = function () {}; - -/** - * A reference to a transaction. - * - * The `Transaction` object passed to a - * transaction's `updateFunction` provides the methods to read and write data - * within the transaction context. See `Firestore.runTransaction()`. - * @interface - */ -firebase.firestore.Transaction = function () {}; - -/** - * Reads the document referenced by the provided `DocumentReference.` - * - * @param {!firebase.firestore.DocumentReference} documentRef - * A reference to the document to be retrieved. - * - * @return {!Promise} - * A promise of the read data in a `DocumentSnapshot`. - */ -firebase.firestore.Transaction.prototype.get = function (documentRef) {}; - -/** - * Writes to the document referred to by the provided `DocumentReference`. - * If the document does not exist yet, it will be created. If you pass - * options, the provided data can be merged into the existing document. - * - * @param {!firebase.firestore.DocumentReference} documentRef - * A reference to the document to be created. - * - * @param {!Object} data - * An object of the fields and values for the document. - * - * @param {!firebase.firestore.SetOptions=} options - * An object to configure the set behavior. Pass `{merge: true}` to only - * replace the values specified in the data argument. Fields omitted - * will remain untouched. - * - * @return {!firebase.firestore.Transaction} - * This `Transaction` instance. Used for chaining method calls. - */ -firebase.firestore.Transaction.prototype.set = function ( - documentRef, - data, - options -) {}; - -/** - * Updates fields in the document referred to by this `DocumentReference`. - * The update will fail if applied to a document that does not exist. - * - * Nested fields can be updated by providing dot-separated field path strings - * or by providing FieldPath objects. - * - * @param {!firebase.firestore.DocumentReference} documentRef - * A reference to the document to be updated. - * - * @param {...*} var_args - * Either an object containing all of the fields and values to update, or a - * series of arguments alternating between fields (as string or - * {@link firebase.firestore.FieldPath} objects) and values. - * - * @return {!firebase.firestore.Transaction} - * This `Transaction` instance. Used for chaining method calls. - */ -firebase.firestore.Transaction.prototype.update = function ( - documentRef, - var_args -) {}; - -/** - * Deletes the document referred to by the provided `DocumentReference`. - * - * @param {!firebase.firestore.DocumentReference} documentRef - * A reference to the document to be deleted. - * - * @return {!firebase.firestore.Transaction} - * This `Transaction` instance. Used for chaining method calls. - */ -firebase.firestore.Transaction.prototype.delete = function (documentRef) {}; - -/** - * A write batch, used to perform multiple writes as a single atomic unit. - * - * A `WriteBatch` object can be acquired by calling the `Firestore.batch()` - * function. It provides methods for adding writes to the write batch. None of - * the writes are committed (or visible locally) until `WriteBatch.commit()` - * is called. - * - * Unlike transactions, write batches are persisted offline and therefore are - * preferable when you don't need to condition your writes on read data. - * @interface - */ -firebase.firestore.WriteBatch = function () {}; - -/** - * Writes to the document referred to by the provided `DocumentReference`. - * If the document does not exist yet, it will be created. If you pass - * options, the provided data can be merged into the existing document. - * - * @param {!firebase.firestore.DocumentReference} documentRef - * A reference to the document to be created. - * - * @param {!Object} data - * An object of the fields and values for the document. - * - * @param {!firebase.firestore.SetOptions=} options - * An object to configure the set behavior. Pass `{merge: true}` to only - * replace the values specified in the data argument. Fields omitted - * will remain untouched. - * - * @return {!firebase.firestore.WriteBatch} - * This `WriteBatch` instance. Used for chaining method calls. - */ -firebase.firestore.WriteBatch.prototype.set = function ( - documentRef, - data, - options -) {}; - -/** - * Updates fields in the document referred to by this `DocumentReference`. - * The update will fail if applied to a document that does not exist. - * - * Nested fields can be updated by providing dot-separated field path strings - * or by providing FieldPath objects. - * - * @param {!firebase.firestore.DocumentReference} documentRef - * A reference to the document to be updated. - * - * @param {...*} var_args - * Either an object containing all of the fields and values to update, or a - * series of arguments alternating between fields (as string or - * {@link firebase.firestore.FieldPath} objects) and values. - * - * @return {!firebase.firestore.WriteBatch} - * This `WriteBatch` instance. Used for chaining method calls. - */ -firebase.firestore.WriteBatch.prototype.update = function ( - documentRef, - var_args -) {}; - -/** - * Deletes the document referred to by the provided `DocumentReference`. - * - * @param {!firebase.firestore.DocumentReference} documentRef - * A reference to the document to be deleted. - * - * @return {!firebase.firestore.WriteBatch} - * This `WriteBatch` instance. Used for chaining method calls. - */ -firebase.firestore.WriteBatch.prototype.delete = function (documentRef) {}; - -/** - * Commits all of the writes in this write batch as a single atomic unit. - * - * @return {!Promise} - * A promise that resolves once all of the writes in the batch have been - * successfully written to the backend as an atomic unit. Note that it won't - * resolve while you're offline. - */ -firebase.firestore.WriteBatch.prototype.commit = function () {}; - -/** - * An options object that configures the behavior of `set()` calls in - * {@link firebase.firestore.DocumentReference#set DocumentReference}, {@link - * firebase.firestore.WriteBatch#set WriteBatch} and {@link - * firebase.firestore.Transaction#set Transaction}. These calls can be - * configured to perform granular merges instead of overwriting the target - * documents in their entirety by providing a `SetOptions` with `merge: true`. - * @interface - */ -firebase.firestore.SetOptions = function () {}; - -/** - * Changes the behavior of a set() call to only replace the values specified - * in its data argument. Fields omitted from the set() call remain untouched. - * - * @type {boolean} - */ -firebase.firestore.SetOptions.prototype.merge; - -/** - * Changes the behavior of set() calls to only replace the specified field - * paths. Any field path that is not specified is ignored and remains - * untouched. - * - * @type {Array|Array} - */ -firebase.firestore.SetOptions.prototype.mergeFields; - -/** - * A `DocumentReference` refers to a document location in a Firestore database - * and can be used to write, read, or listen to the location. The document at - * the referenced location may or may not exist. A `DocumentReference` can - * also be used to create a `CollectionReference` to a subcollection. - * @interface - */ -firebase.firestore.DocumentReference = function () {}; - -/** - * The document's identifier within its collection. - * @type {string} - */ -firebase.firestore.DocumentReference.prototype.id; - -/** - * The {@link firebase.firestore.Firestore} the document is in. - * This is useful for performing transactions, for example. - * @type {!firebase.firestore.Firestore} - */ -firebase.firestore.DocumentReference.prototype.firestore; - -/** - * The Collection this `DocumentReference` belongs to. - * @type {!firebase.firestore.CollectionReference} - */ -firebase.firestore.DocumentReference.prototype.parent; - -/** - * Gets a `CollectionReference` instance that refers to the collection at - * the specified path. - * - * @param {string} collectionPath - * A slash-separated path to a collection. - * - * @return {!firebase.firestore.CollectionReference} - * The `CollectionReference` instance. - */ -firebase.firestore.DocumentReference.prototype.collection = function ( - collectionPath -) {}; - -/** - * Writes to the document referred to by this `DocumentReference`. - * If the document does not exist yet, it will be created. If you pass - * options, the provided data can be merged into the existing document. - * - * @param {!Object} data - * An object of the fields and values for the document. - * - * @param {!firebase.firestore.SetOptions=} options - * An object to configure the set behavior. Pass `{merge: true}` to only - * replace the values specified in the data argument. Fields omitted - * will remain untouched. - * - * @return {!Promise} - * A promise that resolves once the data has been successfully written to the - * backend. (Note that it won't resolve while you're offline). - */ -firebase.firestore.DocumentReference.prototype.set = function ( - data, - options -) {}; - -/** - * Updates fields in the document referred to by this `DocumentReference`. - * The update will fail if applied to a document that does not exist. - * - * Nested fields can be updated by providing dot-separated field path strings - * or by providing FieldPath objects. - * - * @param {...*} var_args - * Either an object containing all of the fields and values to update, or a - * series of arguments alternating between fields (as string or - * {@link firebase.firestore.FieldPath} objects) and values. - * - * @return {!Promise} - * A promise that resolves once the data has been successfully written - * to the backend (Note that it won't resolve while you're offline). - */ -firebase.firestore.DocumentReference.prototype.update = function (var_args) {}; - -/** - * Deletes the document referred to by this `DocumentReference`. - * - * @return {!Promise} - * A promise that resolves once the document has been successfully - * deleted from the backend (Note that it won't resolve while you're offline). - */ -firebase.firestore.DocumentReference.prototype.delete = function () {}; - -/** - * Reads the document referred to by this `DocumentReference`. - * - * @param {!firebase.firestore.GetOptions=} options An options object to - * configure how the data is retrieved. - * - * Note: When using the default behavior, `get()` attempts to provide up-to-date - * data when possible by waiting for data from the server, but may return cached - * data or fail if you are offline and the server cannot be reached. - * - * @return {!Promise} - * A promise that resolves with a `DocumentSnapshot` containing the current - * document contents. - */ -firebase.firestore.DocumentReference.prototype.get = function (options) {}; - -/** - * Attaches a listener for DocumentSnapshot events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single `observer` - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * @param {!firebase.firestore.SnapshotListenOptions|!Object| - function(!firebase.firestore.DocumentSnapshot)} - * optionsOrObserverOrOnNext This can be an observer object or an onNext - * function callback. It can also be an options object containing - * { includeMetadataChanges: true } to opt into events even when only metadata - * changed. - * @param {!Object|function(!firebase.firestore.DocumentSnapshot)| - * function(!Error)} - * observerOrOnNextOrOnError If you provided options, this will be an observer - * object or your onNext callback. Else, it is an optional onError callback. - * @param {function(!Error)=} onError If you didn't provide - * options and didn't use an observer object, this is the optional onError - * callback. - * @return {!function()} An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -firebase.firestore.DocumentReference.prototype.onSnapshot = function ( - optionsOrObserverOrOnNext, - observerOrOnNextOrOnError, - onError -) {}; - -/** - * Options that configure how data is retrieved from a `DocumentSnapshot` - * (e.g. the desired behavior for server timestamps that have not yet been set - * to their final value). - * @interface - */ -firebase.firestore.SnapshotOptions = function () {}; - -/** - * If set, controls the return value for server timestamps that have not yet - * been set to their final value. - * - * By specifying 'estimate', pending server timestamps return an estimate - * based on the local clock. This estimate will differ from the final value - * and cause these values to change once the server result becomes available. - * - * By specifying 'previous', pending timestamps will be ignored and return - * their previous value instead. - * - * If omitted or set to 'none', `null` will be returned by default until the - * server value becomes available. - * - * @type {string|undefined} - */ -firebase.firestore.SnapshotOptions.prototype.serverTimestamps; - -/** - * Options that configure how data is retrieved for a `get()` request. - * @interface - */ -firebase.firestore.GetOptions = function () {}; - -/** - * Describes whether a `get()` call in Firestore should return data from the - * server or from the cache. - * - * Setting to `default` (or not setting at all), causes Firestore to try to - * retrieve an up-to-date (server-retrieved) snapshot, but fall back to - * returning cached data if the server can't be reached. - * - * Setting to `server` causes Firestore to avoid the cache, generating an - * error if the server cannot be reached. Note that the cache will still be - * updated if the server request succeeds. Latency-compensation still takes - * effect and any pending write operations will be visible in the - * returned data (merged into the server-provided data). - * - * Setting to `cache` causes Firestore to immediately return a value from the - * cache, bypassing the server completely. The returned value - * may be stale with respect to the value on the server. If there is no cached - * data, `DocumentReference.get()` will return an error and - * `QuerySnapshot.get()` will return an empty `QuerySnapshot`. - * - * @type {string|undefined} - */ -firebase.firestore.GetOptions.prototype.source; - -/** - * Metadata about a snapshot, describing the state of the snapshot. - * @interface - */ -firebase.firestore.SnapshotMetadata = function () {}; - -/** - * True if the snapshot was created from cached data rather than guaranteed - * up-to-date server data. If your listener has opted into metadata updates - * (via `SnapshotListenOptions`) - * you will receive another snapshot with `fromCache` set to false once - * the client has received up-to-date data from the backend. - * - * @type {boolean} - */ -firebase.firestore.SnapshotMetadata.prototype.fromCache; - -/** - * True if the snapshot includes local writes (`set()` or - * `update()` calls) that haven't been committed to the backend yet. - * - * If your listener has opted into metadata updates via - * `SnapshotListenOptions`, you receive another - * snapshot with `hasPendingWrites` set to false once the writes have been - * committed to the backend. - * - * @type {boolean} - */ -firebase.firestore.SnapshotMetadata.prototype.hasPendingWrites; - -/** - * Returns 'true' if this `SnapshotMetadata` is equal to the provided one. - * - * @param {!firebase.firestore.SnapshotMetadata} other - * The `SnapshotMetadata` to compare against. - * - * @return {boolean} - * 'true' if this `SnapshotMetadata` is equal to the provided one. - */ -firebase.firestore.SnapshotMetadata.prototype.isEqual = function (other) {}; - -/** - * A `DocumentSnapshot` contains data read from a document in your Cloud - * Firestore database. The data can be extracted with `.data()` or - * `.get()` to get a specific field. - * - * For a `DocumentSnapshot` that points to a non-existing document, any data - * access will return 'undefined'. You can use the `exists` property to - * explicitly verify a document's existence. - * - * @constructor - */ -firebase.firestore.DocumentSnapshot = function () {}; - -/** - * Property of the `DocumentSnapshot` that signals whether or not the data - * exists. True if the document exists. - * - * @type {boolean} - */ -firebase.firestore.DocumentSnapshot.prototype.exists; - -/** - * The `DocumentReference` for the document included in the `DocumentSnapshot`. - * - * @type {!firebase.firestore.DocumentReference} - */ -firebase.firestore.DocumentSnapshot.prototype.ref; - -/** - * Property of the `DocumentSnapshot` that provides the document's ID. - * - * @type {string} - */ -firebase.firestore.DocumentSnapshot.prototype.id; - -/** - * Metadata about the `DocumentSnapshot`, including information about its - * source and local modifications. - * - * @type {!firebase.firestore.SnapshotMetadata} - */ -firebase.firestore.DocumentSnapshot.prototype.metadata; - -/** - * An object containing all the data in a document. - * @typedef {Object} - */ -firebase.firestore.DocumentData; - -/** - * Retrieves all fields in the document as an Object. Returns `undefined` if - * the document doesn't exist. - * - * By default, `FieldValue.serverTimestamp()` values that have not yet been - * set to their final value will be returned as `null`. You can override - * this by passing an options object. - * - * @param {!firebase.firestore.SnapshotOptions=} options An options object to - * configure how data is retrieved from the snapshot (e.g. the desired - * behavior for server timestamps that have not yet been set to their final - * value). - * - * @return {(!firebase.firestore.DocumentData|undefined)} - * An object containing all fields in the specified document or 'undefined' - * if the document doesn't exist. - */ -firebase.firestore.DocumentSnapshot.prototype.data = function (options) {}; - -/** - * Retrieves the field specified by `fieldPath`. Returns `undefined` if the - * document or field doesn't exist. - * - * By default, a `FieldValue.serverTimestamp()` that has not yet been set to - * its final value will be returned as `null`. You can override this by - * passing an options object. - * - * @param {(string|!firebase.firestore.FieldPath)} fieldPath - * The path (e.g. 'foo' or 'foo.bar') to a specific field. - * - * @param {!firebase.firestore.SnapshotOptions=} options An options object to - * configure how the field is retrieved from the snapshot (e.g. the desired - * behavior for server timestamps that have not yet been set to their final - * value). - * - * @return {(*|undefined)} - * The data at the specified field location or undefined if no such field - * exists in the document. - */ -firebase.firestore.DocumentSnapshot.prototype.get = function ( - fieldPath, - options -) {}; - -/** - * Returns 'true' if this `DocumentSnapshot` is equal to the provided one. - * - * @param {!firebase.firestore.DocumentSnapshot} other - * The `DocumentSnapshot` to compare against. - * - * @return {boolean} - * 'true' if this `DocumentSnapshot` is equal to the provided one. - */ -firebase.firestore.DocumentSnapshot.prototype.isEqual = function (other) {}; - -/** - * A `QueryDocumentSnapshot` contains data read from a document in your - * Firestore database as part of a query. The document is guaranteed to exist - * and its data can be extracted with `.data()` or `.get()` to get a - * specific field. - * - * A `QueryDocumentSnapshot` offers the same API surface as a - * `DocumentSnapshot`. Since query results contain only existing documents, the - * `exists` property will always be true and `data()` will never return - * `undefined`. - * - * @constructor - * @extends {firebase.firestore.DocumentSnapshot} - */ -firebase.firestore.QueryDocumentSnapshot = function () {}; - -/** - * Retrieves all fields in the document as an Object. - * - * By default, `FieldValue.serverTimestamp()` values that have not yet been - * set to their final value will be returned as `null`. You can override - * this by passing an options object. - * - * @override - * - * @param {!firebase.firestore.SnapshotOptions=} options An options object to - * configure how data is retrieved from the snapshot (e.g. the desired - * behavior for server timestamps that have not yet been set to their - * final value). - * - * @return {!firebase.firestore.DocumentData} - * An object containing all fields in the specified document. - */ -firebase.firestore.QueryDocumentSnapshot.prototype.data = function (options) {}; - -/** - * Options for use with `Query.onSnapshot() to control the behavior of the - * snapshot listener. - * @interface - * - */ -firebase.firestore.SnapshotListenOptions = function () {}; - -/** - * Raise an event even if only metadata of the query or document - * changes. Default is false. - * - * @type {boolean} - */ -firebase.firestore.SnapshotListenOptions.prototype.includeMetadataChanges; - -/** - * A `Query` refers to a Query which you can read or listen to. You can also - * construct refined `Query` objects by adding filters and ordering. - * @constructor - */ -firebase.firestore.Query = function () {}; - -/** - * The `Firestore` for the Cloud Firestore database (useful for performing - * transactions, etc.). - * - * @type {!firebase.firestore.Firestore} - */ -firebase.firestore.Query.prototype.firestore; - -/** - * Creates a new query that returns only documents that include the specified - * fields and where the values satisfy the constraints provided. - * - * @param {(string|!firebase.firestore.FieldPath)} fieldPath - * The path to compare. - * - * @param {string} opStr - * The operation string (e.g "<", "<=", "==", ">", ">="). - * - * @param {*} value - * The value for comparison. - * - * @return {!firebase.firestore.Query} - * The created query. - */ -firebase.firestore.Query.prototype.where = function ( - fieldPath, - opStr, - value -) {}; - -/** - * Creates a new query where the results are sorted by the - * specified field, in descendin or ascending order. - * - * @param {(string|!firebase.firestore.FieldPath)} fieldPath - * The field to sort by. - * - * @param {string=} directionStr - * Optional direction to sort by (`asc` or `desc`). If not specified, the - * default order is ascending. - * - * @return {!firebase.firestore.Query} - * The created query. - */ -firebase.firestore.Query.prototype.orderBy = function ( - fieldPath, - directionStr -) {}; - -/** - * Creates a new query where the results are limited to the specified number of - * documents. - * - * @param {number} limit - * The maximum number of items to return. - * - * @return {!firebase.firestore.Query} - * The created query. - */ -firebase.firestore.Query.prototype.limit = function (limit) {}; - -/** - * Creates a new query where the results start at the provided document - * (inclusive). The starting position is relative to the order of the query. - * The document must contain all of the fields provided in the `orderBy` of - * the query. - * - * @param {...*} snapshotOrVarArgs - * The snapshot of the document you want the query to start at or - * the field values to start this query at, in order of the query's order by. - * - * @return {!firebase.firestore.Query} - * The created query. - */ -firebase.firestore.Query.prototype.startAt = function (snapshotOrVarArgs) {}; - -/** - * Creates a new query where the results start after the provided document - * (exclusive). The starting position is relative to the order of the query. - * The document must contain all of the fields provided in the `orderBy` of - * this query. - * - * @param {...*} snapshotOrVarArgs - * The snapshot of the document to start after or - * the field values to start this query after, in order of the query's order - * by. - * - * @return {!firebase.firestore.Query} - * The created query. - */ -firebase.firestore.Query.prototype.startAfter = function (snapshotOrVarArgs) {}; - -/** - * Creates a new query where the results end before the provided document - * (exclusive). The end position is relative to the order of the query. The - * document must contain all of the fields provided in the `orderBy` of this - * query. - * - * @param {...*} snapshotOrVarArgs - * The snapshot of the document the query results should end before or - * the field values to end this query before, in order of the query's order - * by. - * - * @return {!firebase.firestore.Query} - * The created query. - */ -firebase.firestore.Query.prototype.endBefore = function (snapshotOrVarArgs) {}; - -/** - * Creates a new query where the results end at the provided document - * (inclusive). The end position is relative to the order of the query. The - * document must contain all of the fields provided in the `orderBy` of this - * query. - * - * @param {...*} snapshotOrVarArgs - * The snapshot of the document the query results should end at or - * the field values to end this query at, in order of the query's order by. - * - * @return {!firebase.firestore.Query} - * The created query. - */ -firebase.firestore.Query.prototype.endAt = function (snapshotOrVarArgs) {}; - -/** - * Executes the query and returns the results as a `QuerySnapshot`. - * - * @param {!firebase.firestore.GetOptions=} options An options object to - * configure how the data is retrieved. - * - * @return {!firebase.firestore.QuerySnapshot} - * A promise that will be resolved with the results of the query. - */ -firebase.firestore.Query.prototype.get = function (options) {}; - -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param {!firebase.firestore.SnapshotListenOptions|!Object| - * function(!firebase.firestore.DocumentSnapshot)} - * optionsOrObserverOrOnNext This can be an observer object or an onNext - * function callback. It can also be an options object containing - * { includeMetadataChanges: true } to opt into events even when only metadata - * changed. - * @param {!Object|function(!firebase.firestore.DocumentSnapshot)| - * function(!Error)} - * observerOrOnNextOrOnError If you provided options, this will be an observer - * object or your onNext callback. Else, it is an optional onError callback. - * @param {function(!Error)=} onError If you didn't provide - * options and didn't use an observer object, this is the optional onError - * callback. - * @return {!function()} An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -firebase.firestore.Query.prototype.onSnapshot = function ( - optionsOrObserverOrOnNext, - observerOrOnNextOrOnError, - onError -) {}; - -/** - * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects - * representing the results of a query. The documents can be accessed as an - * array via the `docs` property or enumerated using the `forEach` method. The - * number of documents can be determined via the `empty` and `size` - * properties. - * @interface - */ -firebase.firestore.QuerySnapshot = function () {}; - -/** - * The query you called `get` or `onSnapshot` on to get the `QuerySnapshot`. - * @type {!firebase.firestore.Query} - */ -firebase.firestore.QuerySnapshot.prototype.query; - -/** - * Metadata about this snapshot, concerning its source and if it has local - * modifications. - * @type {!firebase.firestore.SnapshotMetadata} - */ -firebase.firestore.QuerySnapshot.prototype.metadata; - -/** - * Returns an array of the document changes since the last snapshot. If this - * is the first snapshot, all documents will be in the list as "added" changes. - * - * @param {firebase.firestore.SnapshotOptions=} options Options that control - * whether metadata-only changes (i.e. only `DocumentSnapshot.metadata` changed) - * should be included. - */ -firebase.firestore.QuerySnapshot.prototype.docChanges = function (options) {}; - -/** - * An array of all the documents in the `QuerySnapshot`. - * @type {!Array} - */ -firebase.firestore.QuerySnapshot.prototype.docs; - -/** - * The number of documents in the `QuerySnapshot`. - * @type {number} - */ -firebase.firestore.QuerySnapshot.prototype.size; - -/** - * True if there are no documents in the `QuerySnapshot`. - * @type {boolean} - */ -firebase.firestore.QuerySnapshot.prototype.empty; - -/** - * Enumerates all of the documents in the `QuerySnapshot`. - * - * @param {function(!firebase.firestore.QueryDocumentSnapshot)} callback - * @param {*=} thisArg - * The `this` binding for the callback. - */ -firebase.firestore.QuerySnapshot.prototype.forEach = function ( - callback, - thisArg -) {}; - -/** - * Returns 'true' if this `QuerySnapshot` is equal to the provided one. - * - * @param {!firebase.firestore.QuerySnapshot} other - * The `QuerySnapshot` to compare against. - * - * @return {boolean} - * 'true' if this `QuerySnapshot` is equal to the provided one. - */ -firebase.firestore.QuerySnapshot.prototype.isEqual = function (other) {}; - -/** - * A `DocumentChange` represents a change to a document matching a query. - * It contains the document affected and the type of change that occurred. - * @interface - */ -firebase.firestore.DocumentChange = function () {}; - -/** - * The type of change that occurred. - * - * Possible values are 'added', 'modified', or 'removed'. - * @type {string} - */ -firebase.firestore.DocumentChange.prototype.type; - -/** - * The document affected by this change. - * @type {!firebase.firestore.QueryDocumentSnapshot} - */ -firebase.firestore.DocumentChange.prototype.doc; - -/** - * The index of the changed document in the result set immediately prior to - * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects - * have been applied). Is -1 for 'added' events. - * @type {number} - */ -firebase.firestore.DocumentChange.prototype.oldIndex; - -/** - * The index of the changed document in the result set immediately after - * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` - * objects and the current `DocumentChange` object have been applied). - * Is -1 for 'removed' events. - * @type {number} - */ -firebase.firestore.DocumentChange.prototype.newIndex; - -/** - * A `CollectionReference` object can be used for adding documents, getting - * document references, and querying for documents (using the methods - * inherited from `Query`). - * @constructor - * @extends {firebase.firestore.Query} - */ -firebase.firestore.CollectionReference = function () {}; - -/** - * The collection's identifier. - * - * @type {string} - */ -firebase.firestore.CollectionReference.prototype.id; - -/** - * A reference to the containing `DocumentReference` if this is a subcollection. - * If this isn't a subcollection, the reference is null. - * - * @type {?firebase.firestore.DocumentReference} - */ -firebase.firestore.CollectionReference.prototype.parent; - -/** - * Gets a `DocumentReference` for the document within the collection at the - * specified path. If no path is specified, an automatically-generated - * unique ID will be used for the returned `DocumentReference`. - * - * @param {string=} documentPath - * A slash-separated path to a document. - * - * @return {!firebase.firestore.DocumentReference} - */ -firebase.firestore.CollectionReference.prototype.doc = function ( - documentPath -) {}; - -/** - * Adds a new document to this collection with the specified data, assigning - * it a document ID automatically. - * - * @param {!firebase.firestore.DocumentData} data - * @return {!Promise} - * A Promise that resolves with a `DocumentReference` pointing to the newly - * created document after it has been written to the backend. - */ -firebase.firestore.CollectionReference.prototype.add = function (data) {}; - -/** - * Returns 'true' if this `CollectionReference` is equal to the provided one. - * - * @param {!firebase.firestore.CollectionReference} other - * The `CollectionReference` to compare against. - * - * @return {boolean} - * 'true' if this `CollectionReference` is equal to the provided one. - */ -firebase.firestore.CollectionReference.prototype.isEqual = function (other) {}; - -/** - * Sentinel values that can be used when writing document fields with `set()` - * or `update()`. - * @interface - */ -firebase.firestore.FieldValue = function () {}; - -/** - * Returns a sentinel used with `set()` or `update()` to include a - * server-generated timestamp in the written data. - * @return {!firebase.firestore.FieldValue} - */ -firebase.firestore.FieldValue.serverTimestamp = function () {}; - -/** - * Returns a sentinel for use with `update()` to mark a field for deletion. - * @return {!firebase.firestore.FieldValue} - */ -firebase.firestore.FieldValue.delete = function () {}; - -/** - * Returns 'true' if this `FieldValue` is equal to the provided one. - * - * @param {!firebase.firestore.FieldValue} other - * The `FieldValue` to compare against. - * - * @return {boolean} - * 'true' if this `FieldValue` is equal to the provided one. - */ -firebase.firestore.FieldValue.prototype.isEqual = function (other) {}; - -/** - * A FieldPath refers to a field in a document. The path may consist of a - * single field name (referring to a top-level field in the document), or a - * list of field names (referring to a nested field in the document). - * - * Create a FieldPath by providing field names. If more than one field - * name is provided, the path will point to a nested field in a document. - * - * @param {...*} var_args - * A list of field names. - * - * @constructor - */ -firebase.firestore.FieldPath = function (var_args) {}; - -/** - * Returns a special sentinel `FieldPath` to refer to the ID of a document. - * It can be used in queries to sort or filter by the document ID. - * - * @return {!firebase.firestore.FieldPath} - */ -firebase.firestore.FieldPath.documentId = function () {}; - -/** - * Returns 'true' if this `FieldPath` is equal to the provided one. - * - * @param {!firebase.firestore.FieldPath} other - * The `FieldPath` to compare against. - * - * @return {boolean} - * 'true' if this `FieldPath` is equal to the provided one. - */ -firebase.firestore.FieldPath.prototype.isEqual = function (other) {}; - -/** - * The set of Cloud Firestore status codes. These status codes are also exposed - * by [gRPC](https://github.com/grpc/grpc/blob/master/doc/statuscodes.md). - * - * Possible values: - * - `cancelled`: The operation was cancelled (typically by the caller). - * - `unknown`: Unknown error or an error from a different error domain. - * - `invalid-argument`: Client specified an invalid argument. Note that this - * differs from `failed-precondition`. `invalid-argument` indicates - * arguments that are problematic regardless of the state of the system - * (e.g. an invalid field name). - * - `deadline-exceeded`: Deadline expired before operation could complete. - * For operations that change the state of the system, this error may be - * returned even if the operation has completed successfully. For example, - * a successful response from a server could have been delayed long enough - * for the deadline to expire. - * - `not-found`: Some requested document was not found. - * - `already-exists`: Some document that we attempted to create already - * exists. - * - `permission-denied`: The caller does not have permission to execute the - * specified operation. - * - `resource-exhausted`: Some resource has been exhausted, perhaps a - * per-user quota, or perhaps the entire file system is out of space. - * - `failed-precondition`: Operation was rejected because the system is not - * in a state required for the operation`s execution. - * - `aborted`: The operation was aborted, typically due to a concurrency - * issue like transaction aborts, etc. - * - `out-of-range`: Operation was attempted past the valid range. - * - `unimplemented`: Operation is not implemented or not supported/enabled. - * - `internal`: Internal errors. Means some invariants expected by - * underlying system has been broken. If you see one of these errors, - * something is very broken. - * - `unavailable`: The service is currently unavailable. This is most likely - * a transient condition and may be corrected by retrying with a backoff. - * - `data-loss`: Unrecoverable data loss or corruption. - * - `unauthenticated`: The request does not have valid authentication - * credentials for the operation. - * @interface - */ -firebase.firestore.FirestoreError = function () {}; - -/** - * A Timestamp represents a point in time independent of any time zone or - * calendar, represented as seconds and fractions of seconds at nanosecond - * resolution in UTC Epoch time. - * - * Timestamps are encoded using the Proleptic Gregorian Calendar, which extends - * the Gregorian calendar backwards to year one. Timestamps assume all - * minutes are 60 seconds long, i.e. leap seconds are "smeared" so that no leap - * second table is needed for interpretation. Possible timestamp values range - * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. - * - * @see https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto - * - * @param {number} seconds The number of seconds of UTC time since Unix epoch - * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to - * 9999-12-31T23:59:59Z inclusive. - * @param {number} nanoseconds The non-negative fractions of a second at - * nanosecond resolution. Negative second values with fractions must - * still have non-negative nanoseconds values that count forward in time. - * Must be from 0 to 999,999,999 inclusive. - * - * @constructor - */ -firebase.firestore.Timestamp = function (seconds, nanoseconds) {}; - -/** - * Get the current time as a Timestamp object. - * - * @return {!firebase.firestore.Timestamp} a new Timestamp. - */ -firebase.firestore.Timestamp.now = function () {}; - -/** - * Creates a new timestamp from the given date. - * - * @param {!Date} date The date to initialize the `Timestamp` from. - * @return {!firebase.firestore.Timestamp} A new `Timestamp` representing - * the same point in time as the given date. - */ -firebase.firestore.Timestamp.fromDate = function (date) {}; - -/** - * Creates a new timestamp from the given number of milliseconds. - * - * @param {number} milliseconds Number of milliseconds since Unix epoch - * 1970-01-01T00:00:00Z. - * @return {!firebase.firestore.Timestamp} A new `Timestamp` representing the - * same point in time as the given number of milliseconds. - */ -firebase.firestore.Timestamp.fromMillis = function (milliseconds) {}; - -/** - * Convert a Timestamp to a JavaScript `Date` object. This conversion causes - * a loss of precision since `Date` objects only support millisecond precision. - * - * @return {!Date} a JavaScript date object. - */ -firebase.firestore.Timestamp.prototype.toDate = function () {}; - -/** - * Convert a timestamp to a numeric timestamp (in milliseconds since epoch). - * This operation causes a loss of precision. - * - * @return {!number} a numeric timestamp. - */ -firebase.firestore.Timestamp.prototype.toMillis = function () {}; diff --git a/packages/firebase/externs/firebase-messaging-externs.js b/packages/firebase/externs/firebase-messaging-externs.js deleted file mode 100644 index 1b81cde477a..00000000000 --- a/packages/firebase/externs/firebase-messaging-externs.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase Messaging API. - * @externs - */ - -/** - * Gets the {@link firebase.messaging.Messaging `Messaging`} service for the - * default app or a given app. - * - * `firebase.messaging()` can be called with no arguments to access the default - * app's {@link firebase.messaging.Messaging `Messaging`} service or as - * `firebase.messaging(app)` to access the - * {@link firebase.messaging.Messaging `Messaging`} service associated with a - * specific app. - * - * Calling `firebase.messaging()` in a service worker results in Firebase - * generating notifications if the push message payload has a `notification` - * parameter. - * - * @example - * // Get the Messaging service for the default app - * var defaultMessaging = firebase.messaging(); - * - * @example - * // Get the Messaging service for a given app - * var otherMessaging = firebase.messaging(otherApp); - * - * @namespace - * @param {!firebase.app.App=} app The app to create a Messaging service for. - * If not passed, uses the default app. - * - * @return {!firebase.messaging.Messaging} - */ -firebase.messaging = function (app) {}; - -/** - * Gets the {@link firebase.messaging.Messaging `Messaging`} service for the - * current app. - * - * @example - * var messaging = app.messaging(); - * // The above is shorthand for: - * // var messaging = firebase.messaging(app); - * - * @return {!firebase.messaging.Messaging} - */ -firebase.app.App.prototype.messaging = function () {}; - -/** - * The Firebase Messaging service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.messaging `firebase.messaging()`}. - * - * See - * {@link - * https://firebase.google.com/docs/cloud-messaging/js/client - * Set Up a JavaScript Firebase Cloud Messaging Client App} - * for a full guide on how to use the Firebase Messaging service. - * - * @interface - */ -firebase.messaging.Messaging = function () {}; - -/** - * Notification permissions are required to send a user push messages. - * Calling this method displays the permission dialog to the user and - * resolves if the permission is granted. - * - * @return {firebase.Promise} The promise resolves if permission is - * granted. Otherwise, the promise is rejected with an error. - */ -firebase.messaging.Messaging.prototype.requestPermission = function () {}; - -/** - * After calling `requestPermission()` you can call this method to get an FCM - * registration token that can be used to send push messages to this user. - * - * @return {firebase.Promise} The promise resolves if an FCM token can - * be retrieved. This method returns null if the current origin does not have - * permission to show notifications. - */ -firebase.messaging.Messaging.prototype.getToken = function () {}; - -/** - * You should listen for token refreshes so your web app knows when FCM - * has invalidated your existing token and you need to call `getToken()` - * to get a new token. - * - * @param {!firebase.Observer|!function(!Object)} - * nextOrObserver This function, or observer object with `next` defined, - * is called when a token refresh has occurred. - * @return {firebase.Unsubscribe} To stop listening for token - * refresh events execute this returned function. - */ -firebase.messaging.Messaging.prototype.onTokenRefresh = function ( - nextOrObserver -) {}; - -/** - * When a push message is received and the user is currently on a page - * for your origin, the message is passed to the page and an `onMessage()` - * event is dispatched with the payload of the push message. - * - * NOTE: These events are dispatched when you have called - * `setBackgroundMessageHandler()` in your service worker. - * - * @param {!firebase.Observer|!function(!Object)} - * nextOrObserver This function, or observer object with `next` defined, - * is called when a message is received and the user is currently viewing your page. - * @return {firebase.Unsubscribe} To stop listening for messages - * execute this returned function. - */ -firebase.messaging.Messaging.prototype.onMessage = function (nextOrObserver) {}; - -/** - * To forceably stop a registration token from being used, delete it - * by calling this method. - * - * @param {!string} token The token to delete. - * @return {firebase.Promise} The promise resolves when the token has been - * successfully deleted. - */ -firebase.messaging.Messaging.prototype.deleteToken = function (token) {}; - -/** - * To use your own service worker for receiving push messages, you - * can pass in your service worker registration in this method. - * - * @param {!ServiceWorkerRegistration} registration The service worker - * registration you wish to use for push messaging. - */ -firebase.messaging.Messaging.prototype.useServiceWorker = function ( - registration -) {}; - -/** - * FCM directs push messages to your web page's `onMessage()` callback - * if the user currently has it open. Otherwise, it calls - * your callback passed into `setBackgroundMessageHandler()`. - * - * Your callback should return a promise that, once resolved, has - * shown a notification. - * - * @param {!function(!Object)} callback The function to handle the push message. - */ -firebase.messaging.Messaging.prototype.setBackgroundMessageHandler = function ( - callback -) {}; diff --git a/packages/firebase/externs/firebase-storage-externs.js b/packages/firebase/externs/firebase-storage-externs.js deleted file mode 100644 index 388a76232bf..00000000000 --- a/packages/firebase/externs/firebase-storage-externs.js +++ /dev/null @@ -1,670 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Firebase Storage API. - * @externs - */ - -/** - * Gets the {@link firebase.storage.Storage `Storage`} service for the default - * app or a given app. - * - * `firebase.storage()` can be called with no arguments to access the default - * app's {@link firebase.storage.Storage `Storage`} service or as - * `firebase.storage(app)` to access the - * {@link firebase.storage.Storage `Storage`} service associated with a - * specific app. - * - * @example - * // Get the Storage service for the default app - * var defaultStorage = firebase.storage(); - * - * @example - * // Get the Storage service for a given app - * var otherStorage = firebase.storage(otherApp); - * - * @namespace - * @param {!firebase.app.App=} app The app to create a storage service for. - * If not passed, uses the default app. - * - * @return {!firebase.storage.Storage} - */ -firebase.storage = function (app) {}; - -/** - * Gets the {@link firebase.storage.Storage `Storage`} service for the current - * app, optionally initialized with a custom storage bucket. - * - * @example - * var storage = app.storage(); - * // The above is shorthand for: - * // var storage = firebase.storage(app); - * - * @example - * var storage = app.storage("gs://your-app.appspot.com"); - * - * @param {string=} url The gs:// url to your Firebase Storage Bucket. - * If not passed, uses the app's default Storage Bucket. - * @return {!firebase.storage.Storage} - */ -firebase.app.App.prototype.storage = function (url) {}; - -/** - * The Firebase Storage service interface. - * - * Do not call this constructor directly. Instead, use - * {@link firebase.storage `firebase.storage()`}. - * - * See - * {@link - * https://firebase.google.com/docs/storage/web/start/ - * Get Started on Web} - * for a full guide on how to use the Firebase Storage service. - * - * @interface - */ -firebase.storage.Storage = function () {}; - -/** - * The {@link firebase.app.App app} associated with the `Storage` service - * instance. - * - * @example - * var app = storage.app; - * - * @type {!firebase.app.App} - */ -firebase.storage.Storage.prototype.app; - -/** - * Returns a reference for the given path in the default bucket. - * @param {string=} path A relative path to initialize the reference with, - * for example `path/to/image.jpg`. If not passed, the returned reference - * points to the bucket root. - * @return {!firebase.storage.Reference} A reference for the given path. - */ -firebase.storage.Storage.prototype.ref = function (path) {}; - -/** - * Returns a reference for the given absolute URL. - * @param {string} url A URL in the form:
- * 1) a gs:// URL, for example `gs://bucket/files/image.png`
- * 2) a download URL taken from object metadata.
- * @see {@link firebase.storage.FullMetadata.prototype.downloadURLs} - * @return {!firebase.storage.Reference} A reference for the given URL. - */ -firebase.storage.Storage.prototype.refFromURL = function (url) {}; - -/** - * The maximum time to retry operations other than uploads or downloads in - * milliseconds. - * @type {number} - */ -firebase.storage.Storage.prototype.maxOperationRetryTime; - -/** - * @param {number} time The new maximum operation retry time in milliseconds. - * @see {@link firebase.storage.Storage.prototype.maxOperationRetryTime} - */ -firebase.storage.Storage.prototype.setMaxOperationRetryTime = function ( - time -) {}; - -/** - * The maximum time to retry uploads in milliseconds. - * @type {number} - */ -firebase.storage.Storage.prototype.maxUploadRetryTime; - -/** - * @param {number} time The new maximum upload retry time in milliseconds. - * @see {@link firebase.storage.Storage.prototype.maxUploadRetryTime} - */ -firebase.storage.Storage.prototype.setMaxUploadRetryTime = function (time) {}; - -/** - * Represents a reference to a Google Cloud Storage object. Developers can - * upload, download, and delete objects, as well as get/set object metadata. - * @interface - */ -firebase.storage.Reference = function () {}; - -/** - * Returns a gs:// URL for this object in the form - * `gs://///` - * @return {string} The gs:// URL. - */ -firebase.storage.Reference.prototype.toString = function () {}; - -/** - * Returns a reference to a relative path from this reference. - * @param {string} path The relative path from this reference. - * Leading, trailing, and consecutive slashes are removed. - * @return {!firebase.storage.Reference} The reference a the given path. - */ -firebase.storage.Reference.prototype.child = function (path) {}; - -/** - * Uploads data to this reference's location. - * @param {!Blob|!Uint8Array|!ArrayBuffer} data The data to upload. - * @param {!firebase.storage.UploadMetadata=} metadata Metadata for the newly - * uploaded object. - * @return {!firebase.storage.UploadTask} An object that can be used to monitor - * and manage the upload. - */ -firebase.storage.Reference.prototype.put = function (data, metadata) {}; - -/** - * @enum {string} - * An enumeration of the possible string formats for upload. - */ -firebase.storage.StringFormat = { - /** - * Indicates the string should be interpreted "raw", that is, as normal text. - * The string will be interpreted as UTF-16, then uploaded as a UTF-8 byte - * sequence. - * Example: The string 'Hello! \ud83d\ude0a' becomes the byte sequence - * 48 65 6c 6c 6f 21 20 f0 9f 98 8a - */ - RAW: 'raw', - /** - * Indicates the string should be interpreted as base64-encoded data. - * Padding characters (trailing '='s) are optional. - * Example: The string 'rWmO++E6t7/rlw==' becomes the byte sequence - * ad 69 8e fb e1 3a b7 bf eb 97 - */ - BASE64: 'base64', - /** - * Indicates the string should be interpreted as base64url-encoded data. - * Padding characters (trailing '='s) are optional. - * Example: The string 'rWmO--E6t7_rlw==' becomes the byte sequence - * ad 69 8e fb e1 3a b7 bf eb 97 - */ - BASE64URL: 'base64url', - /** - * Indicates the string is a data URL, such as one obtained from - * canvas.toDataURL(). - * Example: the string 'data:application/octet-stream;base64,aaaa' - * becomes the byte sequence - * 69 a6 9a - * (the content-type "application/octet-stream" is also applied, but can - * be overridden in the metadata object). - */ - DATA_URL: 'data_url' -}; - -/** - * Uploads string data to this reference's location. - * @param {string} data The string to upload. - * @param {!firebase.storage.StringFormat=} format The format of the string to - * upload. - * @param {!firebase.storage.UploadMetadata=} metadata Metadata for the newly - * uploaded object. - * @return {!firebase.storage.UploadTask} - * @throws If the format is not an allowed format, or if the given string - * doesn't conform to the specified format. - */ -firebase.storage.Reference.prototype.putString = function ( - data, - format, - metadata -) {}; - -/** - * Deletes the object at this reference's location. - * @return {!firebase.Promise} A Promise that resolves if the deletion - * succeeded and rejects if it failed, including if the object didn't exist. - */ -firebase.storage.Reference.prototype.delete = function () {}; - -/** - * Fetches metadata for the object at this location, if one exists. - * @return {!firebase.Promise} A Promise that - * resolves with the metadata, or rejects if the fetch failed, including if - * the object did not exist. - */ -firebase.storage.Reference.prototype.getMetadata = function () {}; - -/** - * Updates the metadata for the object at this location, if one exists. - * @param {!firebase.storage.SettableMetadata} metadata The new metadata. - * Setting a property to 'null' removes it on the server, while leaving - * a property as 'undefined' has no effect. - * @return {!firebase.Promise} A Promise that - * resolves with the full updated metadata or rejects if the updated failed, - * including if the object did not exist. - */ -firebase.storage.Reference.prototype.updateMetadata = function (metadata) {}; - -/** - * Fetches a long lived download URL for this object. - * @return {!firebase.Promise} A Promise that resolves with the download - * URL or rejects if the fetch failed, including if the object did not - * exist. - */ -firebase.storage.Reference.prototype.getDownloadURL = function () {}; - -/** - * A reference pointing to the parent location of this reference, or null if - * this reference is the root. - * @type {?firebase.storage.Reference} - */ -firebase.storage.Reference.prototype.parent; - -/** - * A reference to the root of this reference's bucket. - * @type {!firebase.storage.Reference} - */ -firebase.storage.Reference.prototype.root; - -/** - * The name of the bucket containing this reference's object. - * @type {string} - */ -firebase.storage.Reference.prototype.bucket; - -/** - * The full path of this object. - * @type {string} - */ -firebase.storage.Reference.prototype.fullPath; - -/** - * The short name of this object, which is the last component of the full path. - * For example, if fullPath is 'full/path/image.png', name is 'image.png'. - * @type {string} - */ -firebase.storage.Reference.prototype.name; - -/** - * The storage service associated with this reference. - * @type {!firebase.storage.Storage} - */ -firebase.storage.Reference.prototype.storage; - -/** - * Object metadata that can be set at any time. - * @interface - */ -firebase.storage.SettableMetadata = function () {}; - -/** - * Served as the 'Cache-Control' header on object download. - * @type {?string|undefined} - */ -firebase.storage.SettableMetadata.prototype.cacheControl; - -/** - * Served as the 'Content-Disposition' header on object download. - * @type {?string|undefined} - */ -firebase.storage.SettableMetadata.prototype.contentDisposition; - -/** - * Served as the 'Content-Encoding' header on object download. - * @type {?string|undefined} - */ -firebase.storage.SettableMetadata.prototype.contentEncoding; - -/** - * Served as the 'Content-Language' header on object download. - * @type {?string|undefined} - */ -firebase.storage.SettableMetadata.prototype.contentLanguage; - -/** - * Served as the 'Content-Type' header on object download. - * @type {?string|undefined} - */ -firebase.storage.SettableMetadata.prototype.contentType; - -/** - * Additional user-defined custom metadata. - * @type {?Object|undefined} - */ -firebase.storage.SettableMetadata.prototype.customMetadata; - -/** - * Object metadata that can be set at upload. - * @interface - * @extends {firebase.storage.SettableMetadata} - */ -firebase.storage.UploadMetadata = function () {}; - -/** - * A Base64-encoded MD5 hash of the object being uploaded. - * @type {?string|undefined} - */ -firebase.storage.UploadMetadata.prototype.md5Hash; - -/** - * The full set of object metadata, including read-only properties. - * @interface - * @extends {firebase.storage.UploadMetadata} - */ -firebase.storage.FullMetadata = function () {}; - -/** - * The bucket this object is contained in. - * @type {string} - */ -firebase.storage.FullMetadata.prototype.bucket; - -/** - * The object's generation. - * @type {string} - * @see {@link https://cloud.google.com/storage/docs/generations-preconditions} - */ -firebase.storage.FullMetadata.prototype.generation; - -/** - * The object's metageneration. - * @type {string} - * @see {@link https://cloud.google.com/storage/docs/generations-preconditions} - */ -firebase.storage.FullMetadata.prototype.metageneration; - -/** - * The full path of this object. - * @type {string} - */ -firebase.storage.FullMetadata.prototype.fullPath; - -/** - * The short name of this object, which is the last component of the full path. - * For example, if fullPath is 'full/path/image.png', name is 'image.png'. - * @type {string} - */ -firebase.storage.FullMetadata.prototype.name; - -/** - * The size of this object, in bytes. - * @type {number} - */ -firebase.storage.FullMetadata.prototype.size; - -/** - * A date string representing when this object was created. - * @type {string} - */ -firebase.storage.FullMetadata.prototype.timeCreated; - -/** - * A date string representing when this object was last updated. - * @type {string} - */ -firebase.storage.FullMetadata.prototype.updated; - -/** - * An array of long-lived download URLs. Always contains at least one URL. - * @type {!Array} - */ -firebase.storage.FullMetadata.prototype.downloadURLs; - -/** - * An event that is triggered on a task. - * @enum {string} - * @see {@link firebase.storage.UploadTask.prototype.on} - */ -firebase.storage.TaskEvent = { - /** - * For this event, - *
    - *
  • The `next` function is triggered on progress updates and when the - * task is paused/resumed with a - * {@link firebase.storage.UploadTaskSnapshot} as the first - * argument.
  • - *
  • The `error` function is triggered if the upload is canceled or fails - * for another reason.
  • - *
  • The `complete` function is triggered if the upload completes - * successfully.
  • - *
- */ - STATE_CHANGED: 'state_changed' -}; - -/** - * Represents the current state of a running upload. - * @enum {string} - */ -firebase.storage.TaskState = { - /** Indicates that the task is still running and making progress. */ - RUNNING: 'running', - /** Indicates that the task is paused. */ - PAUSED: 'paused', - /** Indicates that the task completed successfully. */ - SUCCESS: 'success', - /** Indicates that the task was canceled. */ - CANCELED: 'canceled', - /** Indicates that the task failed for a reason other than being canceled. */ - ERROR: 'error' -}; - -/** - * Represents the process of uploading an object. Allows you to monitor and - * manage the upload. - * @interface - */ -firebase.storage.UploadTask = function () {}; - -/** - * This object behaves like a Promise, and resolves with its snapshot data when - * the upload completes. - * @param {(?function(!firebase.storage.UploadTaskSnapshot):*)=} onFulfilled - * The fulfillment callback. Promise chaining works as normal. - * @param {(?function(!Error):*)=} onRejected The rejection callback. - * @return {!firebase.Promise} - */ -firebase.storage.UploadTask.prototype.then = function ( - onFulfilled, - onRejected -) {}; - -/** - * Equivalent to calling `then(null, onRejected)`. - * @param {!function(!Error):*} onRejected - * @return {!firebase.Promise} - */ -firebase.storage.UploadTask.prototype.catch = function (onRejected) {}; - -/** - * Listens for events on this task. - * - * Events have three callback functions (referred to as `next`, `error`, and - * `complete`). - * - * If only the event is passed, a function that can be used to register the - * callbacks is returned. Otherwise, the callbacks are passed after the event. - * - * Callbacks can be passed either as three separate arguments or as the - * `next`, `error`, and `complete` properties of an object. Any of the three - * callbacks is optional, as long as at least one is specified. In addition, - * when you add your callbacks, you get a function back. You can call this - * function to unregister the associated callbacks. - * - * @example Pass callbacks separately or in an object. - * var next = function(snapshot) {}; - * var error = function(error) {}; - * var complete = function() {}; - * - * // The first example. - * uploadTask.on( - * firebase.storage.TaskEvent.STATE_CHANGED, - * next, - * error, - * complete); - * - * // This is equivalent to the first example. - * uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, { - * 'next': next, - * 'error': error, - * 'complete': complete - * }); - * - * // This is equivalent to the first example. - * var subscribe = uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED); - * subscribe(next, error, complete); - * - * // This is equivalent to the first example. - * var subscribe = uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED); - * subscribe({ - * 'next': next, - * 'error': error, - * 'complete': complete - * }); - * - * @example Any callback is optional. - * // Just listening for completion, this is legal. - * uploadTask.on( - * firebase.storage.TaskEvent.STATE_CHANGED, - * null, - * null, - * function() { - * console.log('upload complete!'); - * }); - * - * // Just listening for progress/state changes, this is legal. - * uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, function(snapshot) { - * var percent = snapshot.bytesTransferred / snapshot.totalBytes * 100; - * console.log(percent + "% done"); - * }); - * - * // This is also legal. - * uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, { - * 'complete': function() { - * console.log('upload complete!'); - * } - * }); - * - * @example Use the returned function to remove callbacks. - * var unsubscribe = uploadTask.on( - * firebase.storage.TaskEvent.STATE_CHANGED, - * function(snapshot) { - * var percent = snapshot.bytesTransferred / snapshot.totalBytes * 100; - * console.log(percent + "% done"); - * // Stop after receiving one update. - * unsubscribe(); - * }); - * - * // This code is equivalent to the above. - * var handle = uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED); - * unsubscribe = handle(function(snapshot) { - * var percent = snapshot.bytesTransferred / snapshot.totalBytes * 100; - * console.log(percent + "% done"); - * // Stop after receiving one update. - * unsubscribe(); - * }); - * - * @param {!firebase.storage.TaskEvent} event The event to listen for. - * @param {(?firebase.Observer| - * ?function(!Object))=} nextOrObserver - * The `next` function, which gets called for each item in - * the event stream, or an observer object with some or all of these three - * properties (`next`, `error`, `complete`). - * @param {?function(!Error)=} error A function that gets called with an Error - * if the event stream ends due to an error. - * @param {?firebase.CompleteFn=} complete A function that gets called if the - * event stream ends normally. - * @return { - * !firebase.Unsubscribe| - * !function(?function(!Object),?function(!Error)=,?firebase.CompleteFn=) - * :!firebase.Unsubscribe} - * If only the event argument is passed, returns a function you can use to - * add callbacks (see the examples above). If more than just the event - * argument is passed, returns a function you can call to unregister the - * callbacks. - */ -firebase.storage.UploadTask.prototype.on = function ( - event, - nextOrObserver, - error, - complete -) {}; - -/** - * Resumes a paused task. Has no effect on a running or failed task. - * @return {boolean} True if the resume had an effect. - */ -firebase.storage.UploadTask.prototype.resume = function () {}; - -/** - * Pauses a running task. Has no effect on a paused or failed task. - * @return {boolean} True if the pause had an effect. - */ -firebase.storage.UploadTask.prototype.pause = function () {}; - -/** - * Cancels a running task. Has no effect on a complete or failed task. - * @return {boolean} True if the cancel had an effect. - */ -firebase.storage.UploadTask.prototype.cancel = function () {}; - -/** - * A snapshot of the current task state. - * @type {!firebase.storage.UploadTaskSnapshot} - */ -firebase.storage.UploadTask.prototype.snapshot; - -/** - * Holds data about the current state of the upload task. - * @interface - */ -firebase.storage.UploadTaskSnapshot = function () {}; - -/** - * The number of bytes that have been successfully uploaded so far. - * @type {number} - */ -firebase.storage.UploadTaskSnapshot.prototype.bytesTransferred; - -/** - * The total number of bytes to be uploaded. - * @type {number} - */ -firebase.storage.UploadTaskSnapshot.prototype.totalBytes; - -/** - * The current state of the task. - * @type {firebase.storage.TaskState} - */ -firebase.storage.UploadTaskSnapshot.prototype.state; - -/** - * Before the upload completes, contains the metadata sent to the server. - * After the upload completes, contains the metadata sent back from the server. - * @type {!firebase.storage.FullMetadata} - */ -firebase.storage.UploadTaskSnapshot.prototype.metadata; - -/** - * After the upload completes, contains a long-lived download URL for the - * object. Also accessible in metadata. - * @type {?string} - */ -firebase.storage.UploadTaskSnapshot.prototype.downloadURL; - -/** - * The task of which this is a snapshot. - * @type {!firebase.storage.UploadTask} - */ -firebase.storage.UploadTaskSnapshot.prototype.task; - -/** - * The reference that spawned this snapshot's upload task. - * @type {!firebase.storage.Reference} - */ -firebase.storage.UploadTaskSnapshot.prototype.ref; diff --git a/packages/firebase/firestore/bundle/index.ts b/packages/firebase/firestore/bundle/index.ts deleted file mode 100644 index d50853e4105..00000000000 --- a/packages/firebase/firestore/bundle/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '@firebase/firestore/bundle'; diff --git a/packages/firebase/firestore/bundle/package.json b/packages/firebase/firestore/bundle/package.json deleted file mode 100644 index 2356dde4c48..00000000000 --- a/packages/firebase/firestore/bundle/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "firebase/firestore/bundle", - "description": "The bundle loading feature of the Cloud Firestore component.", - "main": "dist/index.node.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" -} diff --git a/packages/firebase/firestore/index.cdn.ts b/packages/firebase/firestore/index.cdn.ts deleted file mode 100644 index f8465098c70..00000000000 --- a/packages/firebase/firestore/index.cdn.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '@firebase/firestore'; -import '@firebase/firestore/bundle'; diff --git a/packages/firebase/firestore/index.ts b/packages/firebase/firestore/index.ts index 08e45bd8e42..5d721c4e3b3 100644 --- a/packages/firebase/firestore/index.ts +++ b/packages/firebase/firestore/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -import '@firebase/firestore'; +export * from '@firebase/firestore'; diff --git a/packages-exp/firebase-exp/firestore/lite/index.ts b/packages/firebase/firestore/lite/index.ts similarity index 100% rename from packages-exp/firebase-exp/firestore/lite/index.ts rename to packages/firebase/firestore/lite/index.ts diff --git a/packages/firebase/firestore/lite/package.json b/packages/firebase/firestore/lite/package.json new file mode 100644 index 00000000000..bd35527030d --- /dev/null +++ b/packages/firebase/firestore/lite/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/firestore/lite", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/firestore/lite/index.d.ts" +} diff --git a/packages/firebase/firestore/memory/bundle/index.ts b/packages/firebase/firestore/memory/bundle/index.ts deleted file mode 100644 index 35330d462ee..00000000000 --- a/packages/firebase/firestore/memory/bundle/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This file serves as the public entrypoint for users that import - * `firebase/firestore/memory`. - */ - -import '@firebase/firestore/memory-bundle'; diff --git a/packages/firebase/firestore/memory/bundle/package.json b/packages/firebase/firestore/memory/bundle/package.json deleted file mode 100644 index 457d9c42c1d..00000000000 --- a/packages/firebase/firestore/memory/bundle/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "firebase/firestore/memory", - "description": "The bundle loading feature for the memory-only build of the Cloud Firestore JS SDK.", - "main": "dist/index.node.cjs.js", - "module": "dist/index.esm.js", - "typings": "../../empty-import.d.ts" -} diff --git a/packages/firebase/firestore/memory/index.cdn.ts b/packages/firebase/firestore/memory/index.cdn.ts deleted file mode 100644 index 6f8f730e3d1..00000000000 --- a/packages/firebase/firestore/memory/index.cdn.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This file serves as the public entrypoint for users that import - * `firebase/firestore/memory`. - */ - -import '@firebase/firestore/memory'; -import '@firebase/firestore/memory-bundle'; diff --git a/packages/firebase/firestore/memory/index.ts b/packages/firebase/firestore/memory/index.ts deleted file mode 100644 index 3dcef63e67a..00000000000 --- a/packages/firebase/firestore/memory/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This file serves as the public entrypoint for users that import - * `firebase/firestore/memory`. - */ - -import '@firebase/firestore/memory'; diff --git a/packages/firebase/firestore/memory/package.json b/packages/firebase/firestore/memory/package.json deleted file mode 100644 index 42bf3e7da7f..00000000000 --- a/packages/firebase/firestore/memory/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "firebase/firestore/memory", - "description": "A memory-only build of the Cloud Firestore JS SDK.", - "main": "dist/index.node.cjs.js", - "module": "dist/index.esm.js", - "typings": "../../empty-import.d.ts" -} diff --git a/packages/firebase/firestore/package.json b/packages/firebase/firestore/package.json index e9d7f4bee79..b7e10c3858a 100644 --- a/packages/firebase/firestore/package.json +++ b/packages/firebase/firestore/package.json @@ -1,7 +1,7 @@ { "name": "firebase/firestore", - "description": "The Cloud Firestore component of the Firebase JS SDK.", - "main": "dist/index.node.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/firestore/index.d.ts" } diff --git a/packages/firebase/functions/index.ts b/packages/firebase/functions/index.ts index a0c3711f27b..f824c3ed4dc 100644 --- a/packages/firebase/functions/index.ts +++ b/packages/firebase/functions/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,5 +14,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import '@firebase/functions'; +export * from '@firebase/functions'; diff --git a/packages/firebase/functions/package.json b/packages/firebase/functions/package.json index 36843864f23..410b6de33bb 100644 --- a/packages/firebase/functions/package.json +++ b/packages/firebase/functions/package.json @@ -1,6 +1,7 @@ { "name": "firebase/functions", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/functions/index.d.ts" } diff --git a/packages-exp/firebase-exp/gulpfile.js b/packages/firebase/gulpfile.js similarity index 71% rename from packages-exp/firebase-exp/gulpfile.js rename to packages/firebase/gulpfile.js index a2ceb544e81..0590d9bc9fb 100644 --- a/packages-exp/firebase-exp/gulpfile.js +++ b/packages/firebase/gulpfile.js @@ -16,21 +16,23 @@ */ var gulp = require('gulp'); -var concat = require('gulp-concat'); var sourcemaps = require('gulp-sourcemaps'); +const replace = require('gulp-replace'); -const OUTPUT_FILE = 'firebase.js'; const pkgJson = require('./package.json'); const files = pkgJson.components.map(component => { const componentName = component.replace('/', '-'); return `firebase-${componentName}.js`; }); +const FIREBASE_APP_URL = `https://www.gstatic.com/firebasejs/${pkgJson.version}/firebase-app.js`; -gulp.task('firebase-js', function () { +gulp.task('cdn-type-module-path', function () { return gulp .src(files) .pipe(sourcemaps.init({ loadMaps: true })) - .pipe(concat(OUTPUT_FILE)) + // gulp-replace doesn't work with gulp-sourcemaps, so no change is made to the existing sourcemap. + // Therefore the sourcemap become slightly inaccurate + .pipe(replace('@firebase/app', FIREBASE_APP_URL)) .pipe(sourcemaps.write('.')) .pipe(gulp.dest('.')); }); diff --git a/packages/firebase/index.html b/packages/firebase/index.html deleted file mode 100644 index 601630b8031..00000000000 --- a/packages/firebase/index.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/firebase/installations/index.ts b/packages/firebase/installations/index.ts deleted file mode 100644 index f3a6f5451a2..00000000000 --- a/packages/firebase/installations/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '@firebase/installations'; diff --git a/packages/firebase/installations/package.json b/packages/firebase/installations/package.json deleted file mode 100644 index 4fe3460b122..00000000000 --- a/packages/firebase/installations/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "firebase/installations", - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" -} diff --git a/packages/firebase/messaging/index.ts b/packages/firebase/messaging/index.ts index 0dd280f31e1..2e5c6771b0c 100644 --- a/packages/firebase/messaging/index.ts +++ b/packages/firebase/messaging/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -import '@firebase/messaging'; +export * from '@firebase/messaging'; diff --git a/packages/firebase/messaging/package.json b/packages/firebase/messaging/package.json index d4531e3c2c6..c2bdf639a9e 100644 --- a/packages/firebase/messaging/package.json +++ b/packages/firebase/messaging/package.json @@ -1,6 +1,7 @@ { "name": "firebase/messaging", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/messaging/index.d.ts" } diff --git a/packages-exp/firebase-exp/firestore/index.ts b/packages/firebase/messaging/sw/index.ts similarity index 93% rename from packages-exp/firebase-exp/firestore/index.ts rename to packages/firebase/messaging/sw/index.ts index 5d721c4e3b3..cdeb3895bec 100644 --- a/packages-exp/firebase-exp/firestore/index.ts +++ b/packages/firebase/messaging/sw/index.ts @@ -15,4 +15,4 @@ * limitations under the License. */ -export * from '@firebase/firestore'; +export * from '@firebase/messaging/sw'; diff --git a/packages/firebase/messaging/sw/package.json b/packages/firebase/messaging/sw/package.json new file mode 100644 index 00000000000..df859452d6a --- /dev/null +++ b/packages/firebase/messaging/sw/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/messaging/sw", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/messaging/sw/index.d.ts" +} diff --git a/packages/firebase/package.json b/packages/firebase/package.json index d0e17a19247..2eff5aea615 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -5,15 +5,11 @@ "author": "Firebase (https://firebase.google.com/)", "license": "Apache-2.0", "homepage": "https://firebase.google.com/", - "engines": { - "node": "^8.13.0 || >=10.10.0" - }, "keywords": [ "authentication", "database", "Firebase", "firebase", - "firestore", "realtime", "storage", "performance", @@ -24,40 +20,229 @@ "**/package.json", "/firebase*.js", "/firebase*.map", - "/index.d.ts", - "/empty-import.d.ts", - "/externs" + "compat/index.d.ts" ], + "exports": { + "./analytics": { + "node": { + "require": "./analytics/dist/index.cjs.js", + "import": "./analytics/dist/index.mjs" + }, + "default": "./analytics/dist/index.esm.js" + }, + "./app": { + "node": { + "require": "./app/dist/index.cjs.js", + "import": "./app/dist/index.mjs" + }, + "default": "./app/dist/index.esm.js" + }, + "./app-check": { + "node": { + "require": "./app-check/dist/index.cjs.js", + "import": "./app-check/dist/index.mjs" + }, + "default": "./app-check/dist/index.esm.js" + }, + "./auth": { + "node": { + "require": "./auth/dist/index.cjs.js", + "import": "./auth/dist/index.mjs" + }, + "default": "./auth/dist/index.esm.js" + }, + "./auth/cordova": { + "node": { + "require": "./auth/cordova/dist/index.cjs.js", + "import": "./auth/cordova/dist/index.mjs" + }, + "default": "./auth/cordova/dist/index.esm.js" + }, + "./auth/react-native": { + "node": { + "require": "./auth/react-native/dist/index.cjs.js", + "import": "./auth/react-native/dist/index.mjs" + }, + "default": "./auth/react-native/dist/index.esm.js" + }, + "./database": { + "node": { + "require": "./database/dist/index.cjs.js", + "import": "./database/dist/index.mjs" + }, + "default": "./database/dist/index.esm.js" + }, + "./firestore": { + "node": { + "require": "./firestore/dist/index.cjs.js", + "import": "./firestore/dist/index.mjs" + }, + "default": "./firestore/dist/index.esm.js" + }, + "./firestore/lite": { + "node": { + "require": "./firestore/lite/dist/index.cjs.js", + "import": "./firestore/lite/dist/index.mjs" + }, + "default": "./firestore/lite/dist/index.esm.js" + }, + "./functions": { + "node": { + "require": "./functions/dist/index.cjs.js", + "import": "./functions/dist/index.mjs" + }, + "default": "./functions/dist/index.esm.js" + }, + "./messaging": { + "node": { + "require": "./messaging/dist/index.cjs.js", + "import": "./messaging/dist/index.mjs" + }, + "default": "./messaging/dist/index.esm.js" + }, + "./messaging/sw": { + "node": { + "require": "./messaging/sw/dist/index.cjs.js", + "import": "./messaging/sw/dist/index.mjs" + }, + "default": "./messaging/sw/dist/index.esm.js" + }, + "./performance": { + "node": { + "require": "./performance/dist/index.cjs.js", + "import": "./performance/dist/index.mjs" + }, + "default": "./performance/dist/index.esm.js" + }, + "./remote-config": { + "node": { + "require": "./remote-config/dist/index.cjs.js", + "import": "./remote-config/dist/index.mjs" + }, + "default": "./remote-config/dist/index.esm.js" + }, + "./storage": { + "node": { + "require": "./storage/dist/index.cjs.js", + "import": "./storage/dist/index.mjs" + }, + "default": "./storage/dist/index.esm.js" + }, + "./compat/analytics": { + "node": { + "require": "./compat/analytics/dist/index.cjs.js", + "import": "./compat/analytics/dist/index.mjs" + }, + "default": "./compat/analytics/dist/index.esm.js" + }, + "./compat/app": { + "node": { + "require": "./compat/app/dist/index.cjs.js", + "import": "./compat/app/dist/index.mjs" + }, + "default": "./compat/app/dist/index.esm.js" + }, + "./compat/app-check": { + "node": { + "require": "./compat/app-check/dist/index.cjs.js", + "import": "./compat/app-check/dist/index.mjs" + }, + "default": "./compat/app-check/dist/index.esm.js" + }, + "./compat/auth": { + "node": { + "require": "./compat/auth/dist/index.cjs.js", + "import": "./compat/auth/dist/index.mjs" + }, + "default": "./compat/auth/dist/index.esm.js" + }, + "./compat/database": { + "node": { + "require": "./compat/database/dist/index.cjs.js", + "import": "./compat/database/dist/index.mjs" + }, + "default": "./compat/database/dist/index.esm.js" + }, + "./compat/firestore": { + "node": { + "require": "./compat/firestore/dist/index.cjs.js", + "import": "./compat/firestore/dist/index.mjs" + }, + "default": "./compat/firestore/dist/index.esm.js" + }, + "./compat/functions": { + "node": { + "require": "./compat/functions/dist/index.cjs.js", + "import": "./compat/functions/dist/index.mjs" + }, + "default": "./compat/functions/dist/index.esm.js" + }, + "./compat/messaging": { + "node": { + "require": "./compat/messaging/dist/index.cjs.js", + "import": "./compat/messaging/dist/index.mjs" + }, + "default": "./compat/messaging/dist/index.esm.js" + }, + "./compat/performance": { + "node": { + "require": "./compat/performance/dist/index.cjs.js", + "import": "./compat/performance/dist/index.mjs" + }, + "default": "./compat/performance/dist/index.esm.js" + }, + "./compat/remote-config": { + "node": { + "require": "./compat/remote-config/dist/index.cjs.js", + "import": "./compat/remote-config/dist/index.mjs" + }, + "default": "./compat/remote-config/dist/index.esm.js" + }, + "./compat/storage": { + "node": { + "require": "./compat/storage/dist/index.cjs.js", + "import": "./compat/storage/dist/index.mjs" + }, + "default": "./compat/storage/dist/index.esm.js" + } + }, "repository": { "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, "scripts": { - "build": "rollup -c", - "build:deps": "lerna run --scope firebase --include-dependencies build", + "build": "rollup -c && gulp cdn-type-module-path && yarn build:compat", + "build:compat": "rollup -c compat/rollup.config.js", "dev": "rollup -c -w", "test": "echo 'No test suite for firebase wrapper'", "test:ci": "echo 'No test suite for firebase wrapper'" }, - "main": "dist/index.node.cjs.js", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "react-native": "dist/index.rn.cjs.js", "dependencies": { "@firebase/app": "0.6.30", + "@firebase/app-compat": "0.0.900", "@firebase/app-types": "0.6.3", "@firebase/auth": "0.16.8", + "@firebase/auth-compat": "0.0.900", "@firebase/database": "0.11.0", + "@firebase/database-compat": "0.0.900", "@firebase/firestore": "2.4.0", + "@firebase/firestore-compat": "0.0.900", "@firebase/functions": "0.6.15", + "@firebase/functions-compat": "0.0.900", "@firebase/installations": "0.4.32", "@firebase/messaging": "0.8.0", + "@firebase/messaging-compat": "0.0.900", "@firebase/polyfill": "0.3.36", "@firebase/storage": "0.7.0", + "@firebase/storage-compat": "0.0.900", "@firebase/performance": "0.4.18", + "@firebase/performance-compat": "0.0.900", "@firebase/remote-config": "0.1.43", + "@firebase/remote-config-compat": "0.0.900", "@firebase/analytics": "0.6.18", + "@firebase/analytics-compat": "0.0.900", "@firebase/app-check": "0.3.2", + "@firebase/app-check-compat": "0.0.900", "@firebase/util": "1.3.0" }, "devDependencies": { @@ -69,21 +254,26 @@ "rollup-plugin-terser": "7.0.2", "rollup-plugin-typescript2": "0.30.0", "rollup-plugin-uglify": "6.0.4", + "gulp": "4.0.2", + "gulp-sourcemaps": "3.0.0", + "gulp-replace": "1.1.3", "typescript": "4.2.2" }, - "typings": "index.d.ts", "components": [ "analytics", "app", "app-check", "auth", - "database", - "firestore", + "auth/cordova", + "auth/react-native", "functions", - "installations", - "messaging", + "firestore", + "firestore/lite", "storage", "performance", - "remote-config" + "remote-config", + "messaging", + "messaging/sw", + "database" ] } diff --git a/packages/firebase/performance/index.ts b/packages/firebase/performance/index.ts index fd0da1e7d26..ea17ad60e33 100644 --- a/packages/firebase/performance/index.ts +++ b/packages/firebase/performance/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,5 +14,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import '@firebase/performance'; +export * from '@firebase/performance'; diff --git a/packages/firebase/performance/package.json b/packages/firebase/performance/package.json index e47b2ed6b67..de3d9c555eb 100644 --- a/packages/firebase/performance/package.json +++ b/packages/firebase/performance/package.json @@ -1,6 +1,7 @@ { "name": "firebase/performance", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/performance/index.d.ts" } diff --git a/packages/firebase/remote-config/index.ts b/packages/firebase/remote-config/index.ts index 7858a3370aa..6fee9e72bb6 100644 --- a/packages/firebase/remote-config/index.ts +++ b/packages/firebase/remote-config/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,5 +14,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import '@firebase/remote-config'; +export * from '@firebase/remote-config'; diff --git a/packages/firebase/remote-config/package.json b/packages/firebase/remote-config/package.json index bf7911f3fd6..43b0dd4b3ac 100644 --- a/packages/firebase/remote-config/package.json +++ b/packages/firebase/remote-config/package.json @@ -1,6 +1,7 @@ { "name": "firebase/remote-config", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/remote-config/index.d.ts" } diff --git a/packages/firebase/rollup-internal.config.js b/packages/firebase/rollup-internal.config.js deleted file mode 100644 index f51611c330d..00000000000 --- a/packages/firebase/rollup-internal.config.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Specialized config only for internal deployment to google3 repo, adds required license header to - * generated code. - */ - -// When run in google3, original rollup.config.js will have been renamed to rollup-main.config.js. -import baseBuilds from './rollup-main.config.js'; -import license from 'rollup-plugin-license'; - -const firebaseLicense = license({ - banner: `@license - Copyright ${new Date().getFullYear()} Google LLC. - SPDX-License-Identifier: Apache-2.0` -}); - -const buildsWithLicense = baseBuilds.map(build => { - return Object.assign({}, build, { - plugins: build.plugins.concat(firebaseLicense) - }); -}); - -export default buildsWithLicense; diff --git a/packages/firebase/rollup.config.js b/packages/firebase/rollup.config.js index 55acf8f6c7d..de926c8b3f2 100644 --- a/packages/firebase/rollup.config.js +++ b/packages/firebase/rollup.config.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,74 +15,31 @@ * limitations under the License. */ +import appPkg from './app/package.json'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import pkg from './package.json'; import { resolve } from 'path'; import resolveModule from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; +import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; import sourcemaps from 'rollup-plugin-sourcemaps'; -import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; -import { uglify } from 'rollup-plugin-uglify'; -import { terser } from 'rollup-plugin-terser'; -import json from '@rollup/plugin-json'; -import pkg from './package.json'; - -import appPkg from './app/package.json'; - -import firestorePkg from './firestore/package.json'; -import firestoreBundlePkg from './firestore/bundle/package.json'; - -import firestoreMemoryPkg from './firestore/memory/package.json'; -import firestoreMemoryBundlePkg from './firestore/memory/bundle/package.json'; - -function createUmdOutputConfig(output) { - return { - file: output, - format: 'umd', - sourcemap: true, - extend: true, - name: GLOBAL_NAME, - globals: { - '@firebase/app': GLOBAL_NAME - }, - - /** - * use iife to avoid below error in the old Safari browser - * SyntaxError: Functions cannot be declared in a nested block in strict mode - * https://github.com/firebase/firebase-js-sdk/issues/1228 - * - */ - intro: ` - try { - (function() {`, - outro: ` - }).apply(this, arguments); - } catch(err) { - console.error(err); - throw new Error( - 'Cannot instantiate ${output} - ' + - 'be sure to load firebase-app.js first.' - ); - }` - }; -} -const plugins = [ - sourcemaps(), - resolveModule(), - typescriptPlugin({ - typescript - }), - json(), - commonjs() -]; +const external = Object.keys(pkg.dependencies || {}); +const plugins = [sourcemaps(), resolveModule(), json(), commonjs()]; -const deps = Object.keys(pkg.dependencies || {}); -const external = id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)); +const typescriptPlugin = rollupTypescriptPlugin({ + typescript +}); -/** - * Global UMD Build - */ -const GLOBAL_NAME = 'firebase'; +const typescriptPluginCDN = rollupTypescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + declaration: false + } + } +}); /** * Individual Component Builds @@ -95,31 +52,17 @@ const appBuilds = [ input: 'app/index.ts', output: [ { file: resolve('app', appPkg.main), format: 'cjs', sourcemap: true }, - { file: resolve('app', appPkg.module), format: 'es', sourcemap: true } + { file: resolve('app', appPkg.module), format: 'es', sourcemap: true }, + { file: resolve('app', appPkg.browser), format: 'es', sourcemap: true } ], - plugins, - external - }, - /** - * App UMD Builds - */ - { - input: 'app/index.ts', - output: { - file: 'firebase-app.js', - sourcemap: true, - format: 'umd', - name: GLOBAL_NAME - }, - plugins: [...plugins, uglify()] + plugins: [...plugins, typescriptPlugin], + external: id => external.some(dep => id === dep || id.startsWith(`${dep}/`)) } ]; const componentBuilds = pkg.components // The "app" component is treated differently because it doesn't depend on itself. - // The "firestore" component is treated differently because it contains multiple - // sub components for different builds. - .filter(component => component !== 'app' && component !== 'firestore') + .filter(component => component !== 'app') .map(component => { const pkg = require(`./${component}/package.json`); return [ @@ -135,226 +78,54 @@ const componentBuilds = pkg.components file: resolve(component, pkg.module), format: 'es', sourcemap: true + }, + { + file: resolve(component, pkg.browser), + format: 'es', + sourcemap: true } ], - plugins, - external - }, - { - input: `${component}/index.ts`, - output: createUmdOutputConfig(`firebase-${component}.js`), - plugins: [ - ...plugins, - uglify({ - output: { - ascii_only: true // escape unicode chars - } - }) - ], - external: ['@firebase/app'] + plugins: [...plugins, typescriptPlugin], + external: id => external.some(dep => id === dep || id.startsWith(`${dep}/`)), } ]; }) .reduce((a, b) => a.concat(b), []); -const firestoreBuilds = [ - { - input: `firestore/index.ts`, - output: [ - { - file: resolve('firestore', firestorePkg.main), - format: 'cjs', - sourcemap: true - }, - { - file: resolve('firestore', firestorePkg.module), - format: 'es', - sourcemap: true - } - ], - plugins, - external - }, - { - input: `firestore/bundle/index.ts`, - output: [ - { - file: resolve('firestore/bundle', firestoreBundlePkg.main), - format: 'cjs', - sourcemap: true - }, - { - file: resolve('firestore/bundle', firestoreBundlePkg.module), - format: 'es', - sourcemap: true - } - ], - plugins, - external - }, - { - input: `firestore/index.cdn.ts`, - output: createUmdOutputConfig(`firebase-firestore.js`), - plugins: [ - ...plugins, - uglify({ - output: { - ascii_only: true // escape unicode chars - } - }) - ], - external: ['@firebase/app'] - } -]; - -const firestoreMemoryBuilds = [ - { - input: `firestore/memory/index.ts`, - output: [ - { - file: resolve('firestore/memory', firestoreMemoryPkg.main), - format: 'cjs', - sourcemap: true - }, - { - file: resolve('firestore/memory', firestoreMemoryPkg.module), - format: 'es', - sourcemap: true - } - ], - plugins, - external - }, - { - input: `firestore/memory/bundle/index.ts`, - output: [ - { - file: resolve('firestore/memory/bundle', firestoreMemoryBundlePkg.main), - format: 'cjs', - sourcemap: true - }, - { - file: resolve( - 'firestore/memory/bundle', - firestoreMemoryBundlePkg.module - ), - format: 'es', - sourcemap: true - } - ], - plugins, - external - }, - { - input: `firestore/memory/index.cdn.ts`, - output: createUmdOutputConfig(`firebase-firestore.memory.js`), - plugins: [...plugins, uglify()], - external: ['@firebase/app'] - } -]; - /** - * Complete Package Builds + * CDN script builds */ -const completeBuilds = [ - /** - * App Browser Builds - */ - { - input: 'src/index.ts', - output: [{ file: pkg.module, format: 'es', sourcemap: true }], - plugins, - external - }, - { - input: 'src/index.cdn.ts', - output: { - file: 'firebase.js', - format: 'umd', - sourcemap: true, - name: GLOBAL_NAME - }, - plugins: [...plugins, uglify()] - }, - /** - * App Node.js Builds - */ - { - input: 'src/index.node.ts', - output: { file: pkg.main, format: 'cjs', sourcemap: true }, - plugins, - external - }, - /** - * App React Native Builds - */ +const cdnBuilds = [ { - input: 'src/index.rn.ts', - output: { file: pkg['react-native'], format: 'cjs', sourcemap: true }, - plugins, - external - }, - /** - * Performance script Build - */ - { - input: 'src/index.perf.ts', + input: 'app/index.cdn.ts', output: { - file: 'firebase-performance-standalone.js', - format: 'umd', + file: 'firebase-app.js', sourcemap: true, - name: GLOBAL_NAME + format: 'es' }, - plugins: [ - sourcemaps(), - resolveModule({ - mainFields: ['lite', 'module', 'main'] - }), - typescriptPlugin({ - typescript - }), - json(), - commonjs(), - uglify() - ] + plugins: [...plugins, typescriptPluginCDN] }, - /** - * Performance script Build in ES2017 - */ - { - input: 'src/index.perf.ts', - output: { - file: 'firebase-performance-standalone.es2017.js', - format: 'umd', - sourcemap: true, - name: GLOBAL_NAME - }, - plugins: [ - sourcemaps(), - resolveModule({ - mainFields: ['lite-esm2017', 'esm2017', 'module', 'main'] - }), - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - } - }), - json({ - preferConst: true - }), - commonjs(), - terser() - ] - } -]; + ...pkg.components + .filter(component => component !== 'app') + .map(component => { + // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js + // Otherwise, we will create a directory with '/' in the name. + const componentName = component.replace('/', '-'); -export default [ - ...appBuilds, - ...componentBuilds, - ...firestoreBuilds, - ...firestoreMemoryBuilds, - ...completeBuilds + return { + input: `${component}/index.ts`, + output: { + file: `firebase-${componentName}.js`, + sourcemap: true, + format: 'es' + }, + plugins: [ + ...plugins, + typescriptPluginCDN + ], + external: ['@firebase/app'] + }; + }) ]; + +export default [...appBuilds, ...componentBuilds, ...cdnBuilds]; diff --git a/packages/firebase/src/index.cdn.ts b/packages/firebase/src/index.cdn.ts deleted file mode 100644 index c7a66db422a..00000000000 --- a/packages/firebase/src/index.cdn.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -console.warn(` -It looks like you're using the development build of the Firebase JS SDK. -When deploying Firebase apps to production, it is advisable to only import -the individual SDK components you intend to use. - -For the CDN builds, these are available in the following manner -(replace with the name of a component - i.e. auth, database, etc): - -https://www.gstatic.com/firebasejs/5.0.0/firebase-.js -`); - -import '@firebase/polyfill'; -import firebase from '../app'; -import { name, version } from '../package.json'; - -import '../auth'; -import '../database'; -import '../firestore'; -import '../firestore/bundle'; -import '../functions'; -import '../messaging'; -import '../storage'; -import '../performance'; -import '../analytics'; -import '../remote-config'; -import '../app-check'; - -firebase.registerVersion(name, version, 'cdn'); - -export default firebase; diff --git a/packages/firebase/src/index.node.ts b/packages/firebase/src/index.node.ts deleted file mode 100644 index 2e152f553d3..00000000000 --- a/packages/firebase/src/index.node.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '../app'; -import { name, version } from '../package.json'; - -import '../auth'; -import '../database'; -import '../firestore'; -import '../firestore/bundle'; -import '../functions'; -import '../storage'; - -firebase.registerVersion(name, version, 'node'); - -export default firebase; diff --git a/packages/firebase/src/index.perf.ts b/packages/firebase/src/index.perf.ts deleted file mode 100644 index 00cbb1f5b96..00000000000 --- a/packages/firebase/src/index.perf.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '@firebase/app'; -import '@firebase/performance'; -import { name, version } from '../package.json'; - -firebase.registerVersion(name, version, 'lite'); - -export default firebase; diff --git a/packages/firebase/src/index.rn.ts b/packages/firebase/src/index.rn.ts deleted file mode 100644 index 2123890bf48..00000000000 --- a/packages/firebase/src/index.rn.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '../app'; -import { name, version } from '../package.json'; - -import '../auth'; -import '../database'; -// TODO(b/158625454): Storage doesn't actually work by default in RN (it uses -// `atob`). We should provide a RN build that works out of the box. -import '../storage'; -import '../firestore'; -import '../firestore/bundle'; - -firebase.registerVersion(name, version, 'rn'); - -export default firebase; diff --git a/packages/firebase/src/index.ts b/packages/firebase/src/index.ts deleted file mode 100644 index d518658721e..00000000000 --- a/packages/firebase/src/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -console.warn(` -It looks like you're using the development build of the Firebase JS SDK. -When deploying Firebase apps to production, it is advisable to only import -the individual SDK components you intend to use. - -For the module builds, these are available in the following manner -(replace with the name of a component - i.e. auth, database, etc): - -CommonJS Modules: -const firebase = require('firebase/app'); -require('firebase/'); - -ES Modules: -import firebase from 'firebase/app'; -import 'firebase/'; - -Typescript: -import firebase from 'firebase/app'; -import 'firebase/'; -`); - -import firebase from '../app'; -import { name, version } from '../package.json'; - -import '../auth'; -import '../database'; -import '../firestore'; -import '../firestore/bundle'; -import '../functions'; -import '../messaging'; -import '../storage'; -import '../performance'; -import '../analytics'; -import '../remote-config'; -import '../app-check'; - -firebase.registerVersion(name, version); - -export default firebase; diff --git a/packages/firebase/storage/index.ts b/packages/firebase/storage/index.ts index 62cf25945e7..196d6e4ecd7 100644 --- a/packages/firebase/storage/index.ts +++ b/packages/firebase/storage/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -import '@firebase/storage'; +export * from '@firebase/storage'; diff --git a/packages/firebase/storage/package.json b/packages/firebase/storage/package.json index b1ac09da375..0f0affa9d46 100644 --- a/packages/firebase/storage/package.json +++ b/packages/firebase/storage/package.json @@ -1,6 +1,7 @@ { "name": "firebase/storage", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "typings": "../empty-import.d.ts" + "browser": "dist/index.esm.js", + "module": "dist/index.mjs", + "typings": "dist/storage/index.d.ts" } diff --git a/packages/firebase/tsconfig.json b/packages/firebase/tsconfig.json index 3484a18da7a..a06ed9a374c 100644 --- a/packages/firebase/tsconfig.json +++ b/packages/firebase/tsconfig.json @@ -1,11 +1,9 @@ { "extends": "../../config/tsconfig.base.json", "compilerOptions": { - "outDir": "dist", - "declaration": false, - "allowSyntheticDefaultImports": true + "outDir": "dist" }, "exclude": [ "dist/**/*" ] -} +} \ No newline at end of file diff --git a/packages/firestore-compat/.eslintrc.js b/packages/firestore-compat/.eslintrc.js new file mode 100644 index 00000000000..844d24a0a9f --- /dev/null +++ b/packages/firestore-compat/.eslintrc.js @@ -0,0 +1,76 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + }, + plugins: ['import'], + rules: { + 'no-console': ['error', { allow: ['warn', 'error'] }], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + varsIgnorePattern: '^_', + args: 'none' + } + ], + 'import/order': [ + 'error', + { + 'groups': [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index' + ], + 'newlines-between': 'always', + 'alphabetize': { 'order': 'asc', 'caseInsensitive': true } + } + ] + }, + overrides: [ + { + files: ['**/*.d.ts'], + rules: { + 'camelcase': 'off', + 'import/no-duplicates': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off' + } + }, + { + files: ['**/*.test.ts', '**/test/**/*.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'error' + } + }, + { + files: ['scripts/*.ts'], + rules: { + 'import/no-extraneous-dependencies': 'off', + '@typescript-eslint/no-require-imports': 'off' + } + } + ] +}; diff --git a/packages/firestore-compat/README.md b/packages/firestore-compat/README.md new file mode 100644 index 00000000000..ec4c1e065c4 --- /dev/null +++ b/packages/firestore-compat/README.md @@ -0,0 +1,24 @@ +# @firebase/firestore-compat + +This is the [Cloud Firestore](https://firebase.google.com/docs/firestore/) component of the +[Firebase JS SDK](https://www.npmjs.com/package/firebase). + +**This package is not intended for direct usage, and should only be used via the officially +supported [firebase](https://www.npmjs.com/package/firebase) package.** + +If you are developing a Node.js application that requires administrative access to Cloud Firestore, +use the [`@google-cloud/firestore`](https://www.npmjs.com/package/@google-cloud/firestore) Server +SDK with your developer credentials. + +## Documentation + +For comprehensive documentation please see the [Firebase Reference +Docs][reference-docs]. + +[reference-docs]: https://firebase.google.com/docs/reference/js/ + +## Contributing +See [Contributing to the Firebase SDK](../../CONTRIBUTING.md) for general +information about contributing to the firebase-js-sdk repo and +[Contributing to the Cloud Firestore Component](./CONTRIBUTING.md) for +details specific to the Cloud Firestore code and tests. diff --git a/packages/firestore-compat/karma.conf.js b/packages/firestore-compat/karma.conf.js new file mode 100644 index 00000000000..15b6cdce68a --- /dev/null +++ b/packages/firestore-compat/karma.conf.js @@ -0,0 +1,83 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karmaBase = require('../../config/karma.base'); +const { argv } = require('yargs'); + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: getTestFiles(argv), + + preprocessors: { + 'test/**/*.ts': ['webpack', 'sourcemap'] + }, + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'], + + client: Object.assign({}, karmaBase.client, { + firestoreSettings: getFirestoreSettings(argv) + }) + }); + + config.set(karmaConfig); +}; + +/** + * Gets the list of files to execute, based on existence of the + * --unit and --integration command-line arguments. + */ +function getTestFiles(argv) { + const unitTests = 'test/unit/bootstrap.ts'; + const legcayIntegrationTests = 'test/integration/bootstrap.ts'; + const liteIntegrationTests = 'test/lite/bootstrap.ts'; + if (argv.unit) { + return [unitTests]; + } else if (argv.integration) { + return [legcayIntegrationTests]; + } else if (argv.lite) { + process.env.TEST_PLATFORM = 'browser_lite'; + return [liteIntegrationTests]; + } else { + // Note that we cannot include both the firestore-exp and the legacy SDK + // as the test runners modify the global namespace cannot be both included + // in the same bundle. + return [unitTests, legcayIntegrationTests]; + } +} + +/** + * If the --local argument is passed, returns a {host, ssl} FirestoreSettings + * object that point to localhost instead of production. + */ +function getFirestoreSettings(argv) { + if (argv.local) { + return { + host: 'localhost:8080', + ssl: false + }; + } else { + return { + host: 'firestore.googleapis.com', + ssl: true + }; + } +} + +module.exports.files = getTestFiles(argv); diff --git a/packages/firestore-compat/package.json b/packages/firestore-compat/package.json new file mode 100644 index 00000000000..f746330ed4d --- /dev/null +++ b/packages/firestore-compat/package.json @@ -0,0 +1,46 @@ +{ + "name": "@firebase/firestore-compat", + "version": "0.0.900", + "description": "The Cloud Firestore component of the Firebase JS SDK.", + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.node.cjs.js", + "react-native": "dist/index.rn.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "esm5": "dist/index.esm5.js", + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "prettier": "prettier --write '*.js' '*.ts' '@(src|test)/**/*.ts'", + "build": "rollup -c ./rollup.config.js", + "build:console": "node tools/console.build.js", + "build:deps": "lerna run --scope @firebase/firestore-compat --include-dependencies build", + "build:release": "yarn build && yarn add-compat-overloads", + "test": "echo 'tested as part of firestore'", + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../firestore/dist/index.d.ts -o dist/src/index.d.ts -a -r Firestore:types.FirebaseFirestore -r CollectionReference:types.CollectionReference -r DocumentReference:types.DocumentReference -r Query:types.Query -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/firestore" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "dependencies": { + "@firebase/component": "0.5.6", + "@firebase/firestore": "2.4.0", + "@firebase/util": "1.3.0", + "@firebase/firestore-types": "2.4.0", + "tslib": "^2.1.0" + }, + "devDependencies": { + "@firebase/app-compat": "0.0.900", + "@types/eslint": "7.2.10", + "rollup": "2.52.2", + "rollup-plugin-sourcemaps": "0.6.3", + "rollup-plugin-terser": "7.0.2", + "rollup-plugin-typescript2": "0.30.0", + "@rollup/plugin-node-resolve": "11.2.0", + "ts-node": "9.1.1", + "typescript": "4.2.2" + }, + "license": "Apache-2.0", + "typings": "dist/src/index.d.ts" + } + \ No newline at end of file diff --git a/packages/firestore-compat/rollup.config.js b/packages/firestore-compat/rollup.config.js new file mode 100644 index 00000000000..4117d56023f --- /dev/null +++ b/packages/firestore-compat/rollup.config.js @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import pkg from './package.json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; + +const util = require('../firestore/rollup.shared'); + +const deps = Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }); + +const es2017Plugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + transformers: [util.removeAssertTransformer] + }), + json({ preferConst: true }) +]; + +const es5Plugings = [ + typescriptPlugin({ + typescript, + transformers: [util.removeAssertTransformer] + }), + json({ preferConst: true }) +]; + +const browserBuilds = [ + { + input: './src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + plugins: es2017Plugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + { + input: './src/index.ts', + output: [ + { + file: pkg.esm5, + format: 'es', + sourcemap: true + } + ], + plugins: es5Plugings, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +const nodeBuilds = [ + { + input: './src/index.node.ts', + output: { + file: pkg.main, + format: 'es', + sourcemap: true + }, + plugins: es2017Plugins, + external: deps + } +]; + +const rnBuilds = [ + { + input: './src/index.rn.ts', + output: { + file: pkg['react-native'], + format: 'es', + sourcemap: true + }, + plugins: es2017Plugins, + external: deps + } +]; + +export default [...browserBuilds, ...nodeBuilds, ...rnBuilds]; diff --git a/packages/firestore/src/api/blob.ts b/packages/firestore-compat/src/api/blob.ts similarity index 89% rename from packages/firestore/src/api/blob.ts rename to packages/firestore-compat/src/api/blob.ts index 37ee3f54411..e79f8220500 100644 --- a/packages/firestore/src/api/blob.ts +++ b/packages/firestore-compat/src/api/blob.ts @@ -15,17 +15,14 @@ * limitations under the License. */ +import { Bytes, FirestoreError, _isBase64Available } from '@firebase/firestore'; import { Compat } from '@firebase/util'; -import { Bytes } from '../../exp/index'; -import { isBase64Available } from '../platform/base64'; -import { Code, FirestoreError } from '../util/error'; - /** Helper function to assert Uint8Array is available at runtime. */ function assertUint8ArrayAvailable(): void { if (typeof Uint8Array === 'undefined') { throw new FirestoreError( - Code.UNIMPLEMENTED, + 'unimplemented', 'Uint8Arrays are not available in this environment.' ); } @@ -33,9 +30,9 @@ function assertUint8ArrayAvailable(): void { /** Helper function to assert Base64 functions are available at runtime. */ function assertBase64Available(): void { - if (!isBase64Available()) { + if (!_isBase64Available()) { throw new FirestoreError( - Code.UNIMPLEMENTED, + 'unimplemented', 'Blobs are unavailable in Firestore in this environment.' ); } diff --git a/packages/firestore-compat/src/api/database.ts b/packages/firestore-compat/src/api/database.ts new file mode 100644 index 00000000000..c027e9177e5 --- /dev/null +++ b/packages/firestore-compat/src/api/database.ts @@ -0,0 +1,1339 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '@firebase/app-types'; +import { _FirebaseApp, FirebaseService } from '@firebase/app-types/private'; +import { + LoadBundleTask, + Bytes, + clearIndexedDbPersistence, + disableNetwork, + enableIndexedDbPersistence, + enableMultiTabIndexedDbPersistence, + enableNetwork, + ensureFirestoreConfigured, + Firestore as ExpFirestore, + connectFirestoreEmulator, + waitForPendingWrites, + FieldPath as ExpFieldPath, + limit, + limitToLast, + where, + orderBy, + startAfter, + startAt, + query, + endBefore, + endAt, + doc, + collection, + collectionGroup, + queryEqual, + Query as ExpQuery, + CollectionReference as ExpCollectionReference, + DocumentReference as ExpDocumentReference, + refEqual, + addDoc, + deleteDoc, + executeWrite, + getDoc, + getDocFromCache, + getDocFromServer, + getDocs, + getDocsFromCache, + getDocsFromServer, + onSnapshot, + onSnapshotsInSync, + setDoc, + updateDoc, + Unsubscribe, + DocumentChange as ExpDocumentChange, + DocumentSnapshot as ExpDocumentSnapshot, + QueryDocumentSnapshot as ExpQueryDocumentSnapshot, + QuerySnapshot as ExpQuerySnapshot, + snapshotEqual, + SnapshotMetadata, + runTransaction, + Transaction as ExpTransaction, + WriteBatch as ExpWriteBatch, + AbstractUserDataWriter, + FirestoreError, + FirestoreDataConverter as ModularFirestoreDataConverter, + setLogLevel as setClientLogLevel, + _DatabaseId, + _validateIsNotUsedTogether, + _cast, + _DocumentKey, + _debugAssert, + _FieldPath, + _ResourcePath, + _ByteString, + _logWarn, + namedQuery, + loadBundle, + PartialWithFieldValue, + WithFieldValue +} from '@firebase/firestore'; +import { + CollectionReference as PublicCollectionReference, + DocumentChange as PublicDocumentChange, + DocumentChangeType as PublicDocumentChangeType, + DocumentData, + DocumentData as PublicDocumentData, + DocumentReference as PublicDocumentReference, + DocumentSnapshot as PublicDocumentSnapshot, + FieldPath as PublicFieldPath, + FirebaseFirestore as PublicFirestore, + FirestoreDataConverter as PublicFirestoreDataConverter, + GetOptions as PublicGetOptions, + LogLevel as PublicLogLevel, + OrderByDirection as PublicOrderByDirection, + PersistenceSettings as PublicPersistenceSettings, + Query as PublicQuery, + QueryDocumentSnapshot as PublicQueryDocumentSnapshot, + QuerySnapshot as PublicQuerySnapshot, + SetOptions as PublicSetOptions, + Settings as PublicSettings, + SnapshotListenOptions as PublicSnapshotListenOptions, + SnapshotOptions as PublicSnapshotOptions, + Transaction as PublicTransaction, + UpdateData as PublicUpdateData, + WhereFilterOp as PublicWhereFilterOp, + WriteBatch as PublicWriteBatch +} from '@firebase/firestore-types'; +import { + Compat, + EmulatorMockTokenOptions, + getModularInstance +} from '@firebase/util'; + +import { validateSetOptions } from '../util/input_validation'; + +import { Blob } from './blob'; +import { + CompleteFn, + ErrorFn, + isPartialObserver, + NextFn, + PartialObserver +} from './observer'; + +/** + * A persistence provider for either memory-only or IndexedDB persistence. + * Mainly used to allow optional inclusion of IndexedDB code. + */ +export interface PersistenceProvider { + enableIndexedDbPersistence( + firestore: Firestore, + forceOwnership: boolean + ): Promise; + enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise; + clearIndexedDbPersistence(firestore: Firestore): Promise; +} + +const MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE = + 'You are using the memory-only build of Firestore. Persistence support is ' + + 'only available via the @firebase/firestore bundle or the ' + + 'firebase-firestore.js build.'; + +/** + * The persistence provider included with the memory-only SDK. This provider + * errors for all attempts to access persistence. + */ +export class MemoryPersistenceProvider implements PersistenceProvider { + enableIndexedDbPersistence( + firestore: Firestore, + forceOwnership: boolean + ): Promise { + throw new FirestoreError( + 'failed-precondition', + MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE + ); + } + + enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise { + throw new FirestoreError( + 'failed-precondition', + MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE + ); + } + + clearIndexedDbPersistence(firestore: Firestore): Promise { + throw new FirestoreError( + 'failed-precondition', + MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE + ); + } +} + +/** + * The persistence provider included with the full Firestore SDK. + */ +export class IndexedDbPersistenceProvider implements PersistenceProvider { + enableIndexedDbPersistence( + firestore: Firestore, + forceOwnership: boolean + ): Promise { + return enableIndexedDbPersistence(firestore._delegate, { forceOwnership }); + } + enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise { + return enableMultiTabIndexedDbPersistence(firestore._delegate); + } + clearIndexedDbPersistence(firestore: Firestore): Promise { + return clearIndexedDbPersistence(firestore._delegate); + } +} + +/** + * Compat class for Firestore. Exposes Firestore Legacy API, but delegates + * to the functional API of firestore-exp. + */ +export class Firestore + implements PublicFirestore, FirebaseService, Compat +{ + _appCompat?: FirebaseApp; + constructor( + databaseIdOrApp: _DatabaseId | FirebaseApp, + readonly _delegate: ExpFirestore, + private _persistenceProvider: PersistenceProvider + ) { + if (!(databaseIdOrApp instanceof _DatabaseId)) { + this._appCompat = databaseIdOrApp as FirebaseApp; + } + } + + get _databaseId(): _DatabaseId { + return this._delegate._databaseId; + } + + settings(settingsLiteral: PublicSettings): void { + const currentSettings = this._delegate._getSettings(); + if ( + !settingsLiteral.merge && + currentSettings.host !== settingsLiteral.host + ) { + _logWarn( + 'You are overriding the original host. If you did not intend ' + + 'to override your settings, use {merge: true}.' + ); + } + + if (settingsLiteral.merge) { + settingsLiteral = { + ...currentSettings, + ...settingsLiteral + }; + // Remove the property from the settings once the merge is completed + delete settingsLiteral.merge; + } + + this._delegate._setSettings(settingsLiteral); + } + + useEmulator( + host: string, + port: number, + options: { + mockUserToken?: EmulatorMockTokenOptions | string; + } = {} + ): void { + connectFirestoreEmulator(this._delegate, host, port, options); + } + + enableNetwork(): Promise { + return enableNetwork(this._delegate); + } + + disableNetwork(): Promise { + return disableNetwork(this._delegate); + } + + enablePersistence(settings?: PublicPersistenceSettings): Promise { + let synchronizeTabs = false; + let experimentalForceOwningTab = false; + + if (settings) { + synchronizeTabs = !!settings.synchronizeTabs; + experimentalForceOwningTab = !!settings.experimentalForceOwningTab; + + _validateIsNotUsedTogether( + 'synchronizeTabs', + synchronizeTabs, + 'experimentalForceOwningTab', + experimentalForceOwningTab + ); + } + + return synchronizeTabs + ? this._persistenceProvider.enableMultiTabIndexedDbPersistence(this) + : this._persistenceProvider.enableIndexedDbPersistence( + this, + experimentalForceOwningTab + ); + } + + clearPersistence(): Promise { + return this._persistenceProvider.clearIndexedDbPersistence(this); + } + + terminate(): Promise { + if (this._appCompat) { + (this._appCompat as _FirebaseApp)._removeServiceInstance( + 'firestore-compat' + ); + (this._appCompat as _FirebaseApp)._removeServiceInstance('firestore'); + } + return this._delegate._delete(); + } + + waitForPendingWrites(): Promise { + return waitForPendingWrites(this._delegate); + } + + onSnapshotsInSync(observer: PartialObserver): Unsubscribe; + onSnapshotsInSync(onSync: () => void): Unsubscribe; + onSnapshotsInSync(arg: unknown): Unsubscribe { + return onSnapshotsInSync(this._delegate, arg as PartialObserver); + } + + get app(): FirebaseApp { + if (!this._appCompat) { + throw new FirestoreError( + 'failed-precondition', + "Firestore was not initialized using the Firebase SDK. 'app' is " + + 'not available' + ); + } + return this._appCompat as FirebaseApp; + } + + INTERNAL = { + delete: () => this.terminate() + }; + + collection(pathString: string): PublicCollectionReference { + try { + return new CollectionReference( + this, + collection(this._delegate, pathString) + ); + } catch (e) { + throw replaceFunctionName(e, 'collection()', 'Firestore.collection()'); + } + } + + doc(pathString: string): PublicDocumentReference { + try { + return new DocumentReference(this, doc(this._delegate, pathString)); + } catch (e) { + throw replaceFunctionName(e, 'doc()', 'Firestore.doc()'); + } + } + + collectionGroup(collectionId: string): PublicQuery { + try { + return new Query(this, collectionGroup(this._delegate, collectionId)); + } catch (e) { + throw replaceFunctionName( + e, + 'collectionGroup()', + 'Firestore.collectionGroup()' + ); + } + } + + runTransaction( + updateFunction: (transaction: PublicTransaction) => Promise + ): Promise { + return runTransaction(this._delegate, transaction => + updateFunction(new Transaction(this, transaction)) + ); + } + + batch(): PublicWriteBatch { + ensureFirestoreConfigured(this._delegate); + return new WriteBatch( + new ExpWriteBatch(this._delegate, mutations => + executeWrite(this._delegate, mutations) + ) + ); + } + + loadBundle( + bundleData: ArrayBuffer | ReadableStream | string + ): LoadBundleTask { + return loadBundle(this._delegate, bundleData); + } + + namedQuery(name: string): Promise | null> { + return namedQuery(this._delegate, name).then(expQuery => { + if (!expQuery) { + return null; + } + return new Query( + this, + // We can pass `expQuery` here directly since named queries don't have a UserDataConverter. + // Otherwise, we would have to create a new ExpQuery and pass the old UserDataConverter. + expQuery + ); + }); + } +} + +export class UserDataWriter extends AbstractUserDataWriter { + constructor(protected firestore: Firestore) { + super(); + } + + protected convertBytes(bytes: _ByteString): Blob { + return new Blob(new Bytes(bytes)); + } + + protected convertReference(name: string): DocumentReference { + const key = this.convertDocumentKey(name, this.firestore._databaseId); + return DocumentReference.forKey(key, this.firestore, /* converter= */ null); + } +} + +export function setLogLevel(level: PublicLogLevel): void { + setClientLogLevel(level); +} + +/** + * A reference to a transaction. + */ +export class Transaction implements PublicTransaction, Compat { + private _userDataWriter: UserDataWriter; + + constructor( + private readonly _firestore: Firestore, + readonly _delegate: ExpTransaction + ) { + this._userDataWriter = new UserDataWriter(_firestore); + } + + get( + documentRef: PublicDocumentReference + ): Promise> { + const ref = castReference(documentRef); + return this._delegate + .get(ref) + .then( + result => + new DocumentSnapshot( + this._firestore, + new ExpDocumentSnapshot( + this._firestore._delegate, + this._userDataWriter, + result._key, + result._document, + result.metadata, + ref.converter + ) + ) + ); + } + + set( + documentRef: DocumentReference, + data: Partial, + options: PublicSetOptions + ): Transaction; + set(documentRef: DocumentReference, data: T): Transaction; + set( + documentRef: PublicDocumentReference, + data: T | Partial, + options?: PublicSetOptions + ): Transaction { + const ref = castReference(documentRef); + if (options) { + validateSetOptions('Transaction.set', options); + this._delegate.set(ref, data as PartialWithFieldValue, options); + } else { + this._delegate.set(ref, data as WithFieldValue); + } + return this; + } + + update( + documentRef: PublicDocumentReference, + data: PublicUpdateData + ): Transaction; + update( + documentRef: PublicDocumentReference, + field: string | PublicFieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): Transaction; + update( + documentRef: PublicDocumentReference, + dataOrField: unknown, + value?: unknown, + ...moreFieldsAndValues: unknown[] + ): Transaction { + const ref = castReference(documentRef); + if (arguments.length === 2) { + this._delegate.update(ref, dataOrField as PublicUpdateData); + } else { + this._delegate.update( + ref, + dataOrField as string | ExpFieldPath, + value, + ...moreFieldsAndValues + ); + } + + return this; + } + + delete(documentRef: PublicDocumentReference): Transaction { + const ref = castReference(documentRef); + this._delegate.delete(ref); + return this; + } +} + +export class WriteBatch implements PublicWriteBatch, Compat { + constructor(readonly _delegate: ExpWriteBatch) {} + set( + documentRef: DocumentReference, + data: Partial, + options: PublicSetOptions + ): WriteBatch; + set(documentRef: DocumentReference, data: T): WriteBatch; + set( + documentRef: PublicDocumentReference, + data: T | Partial, + options?: PublicSetOptions + ): WriteBatch { + const ref = castReference(documentRef); + if (options) { + validateSetOptions('WriteBatch.set', options); + this._delegate.set(ref, data as PartialWithFieldValue, options); + } else { + this._delegate.set(ref, data as WithFieldValue); + } + return this; + } + + update( + documentRef: PublicDocumentReference, + data: PublicUpdateData + ): WriteBatch; + update( + documentRef: PublicDocumentReference, + field: string | PublicFieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): WriteBatch; + update( + documentRef: PublicDocumentReference, + dataOrField: string | PublicFieldPath | PublicUpdateData, + value?: unknown, + ...moreFieldsAndValues: unknown[] + ): WriteBatch { + const ref = castReference(documentRef); + if (arguments.length === 2) { + this._delegate.update(ref, dataOrField as PublicUpdateData); + } else { + this._delegate.update( + ref, + dataOrField as string | ExpFieldPath, + value, + ...moreFieldsAndValues + ); + } + return this; + } + + delete(documentRef: PublicDocumentReference): WriteBatch { + const ref = castReference(documentRef); + this._delegate.delete(ref); + return this; + } + + commit(): Promise { + return this._delegate.commit(); + } +} + +/** + * Wraps a `PublicFirestoreDataConverter` translating the types from the + * experimental SDK into corresponding types from the Classic SDK before passing + * them to the wrapped converter. + */ +class FirestoreDataConverter + implements + ModularFirestoreDataConverter, + Compat> +{ + private static readonly INSTANCES = new WeakMap(); + + private constructor( + private readonly _firestore: Firestore, + private readonly _userDataWriter: UserDataWriter, + readonly _delegate: PublicFirestoreDataConverter + ) {} + + fromFirestore( + snapshot: ExpQueryDocumentSnapshot, + options?: PublicSnapshotOptions + ): U { + const expSnapshot = new ExpQueryDocumentSnapshot( + this._firestore._delegate, + this._userDataWriter, + snapshot._key, + snapshot._document, + snapshot.metadata, + /* converter= */ null + ); + return this._delegate.fromFirestore( + new QueryDocumentSnapshot(this._firestore, expSnapshot), + options ?? {} + ); + } + + toFirestore(modelObject: WithFieldValue): PublicDocumentData; + toFirestore( + modelObject: PartialWithFieldValue, + options: PublicSetOptions + ): PublicDocumentData; + toFirestore( + modelObject: WithFieldValue | PartialWithFieldValue, + options?: PublicSetOptions + ): PublicDocumentData { + if (!options) { + return this._delegate.toFirestore(modelObject as U); + } else { + return this._delegate.toFirestore(modelObject as Partial, options); + } + } + + // Use the same instance of `FirestoreDataConverter` for the given instances + // of `Firestore` and `PublicFirestoreDataConverter` so that isEqual() will + // compare equal for two objects created with the same converter instance. + static getInstance( + firestore: Firestore, + converter: PublicFirestoreDataConverter + ): FirestoreDataConverter { + const converterMapByFirestore = FirestoreDataConverter.INSTANCES; + let untypedConverterByConverter = converterMapByFirestore.get(firestore); + if (!untypedConverterByConverter) { + untypedConverterByConverter = new WeakMap(); + converterMapByFirestore.set(firestore, untypedConverterByConverter); + } + + let instance = untypedConverterByConverter.get(converter); + if (!instance) { + instance = new FirestoreDataConverter( + firestore, + new UserDataWriter(firestore), + converter + ); + untypedConverterByConverter.set(converter, instance); + } + + return instance; + } +} + +/** + * A reference to a particular document in a collection in the database. + */ +export class DocumentReference + implements PublicDocumentReference, Compat> +{ + private _userDataWriter: UserDataWriter; + + constructor( + readonly firestore: Firestore, + readonly _delegate: ExpDocumentReference + ) { + this._userDataWriter = new UserDataWriter(firestore); + } + + static forPath( + path: _ResourcePath, + firestore: Firestore, + converter: ModularFirestoreDataConverter | null + ): DocumentReference { + if (path.length % 2 !== 0) { + throw new FirestoreError( + 'invalid-argument', + 'Invalid document reference. Document ' + + 'references must have an even number of segments, but ' + + `${path.canonicalString()} has ${path.length}` + ); + } + return new DocumentReference( + firestore, + new ExpDocumentReference( + firestore._delegate, + converter, + new _DocumentKey(path) + ) + ); + } + + static forKey( + key: _DocumentKey, + firestore: Firestore, + converter: ModularFirestoreDataConverter | null + ): DocumentReference { + return new DocumentReference( + firestore, + new ExpDocumentReference(firestore._delegate, converter, key) + ); + } + + get id(): string { + return this._delegate.id; + } + + get parent(): PublicCollectionReference { + return new CollectionReference(this.firestore, this._delegate.parent); + } + + get path(): string { + return this._delegate.path; + } + + collection( + pathString: string + ): PublicCollectionReference { + try { + return new CollectionReference( + this.firestore, + collection(this._delegate, pathString) + ); + } catch (e) { + throw replaceFunctionName( + e, + 'collection()', + 'DocumentReference.collection()' + ); + } + } + + isEqual(other: PublicDocumentReference): boolean { + other = getModularInstance>(other); + + if (!(other instanceof ExpDocumentReference)) { + return false; + } + return refEqual(this._delegate, other); + } + + set(value: Partial, options: PublicSetOptions): Promise; + set(value: T): Promise; + set(value: T | Partial, options?: PublicSetOptions): Promise { + options = validateSetOptions('DocumentReference.set', options); + try { + if (options) { + return setDoc( + this._delegate, + value as PartialWithFieldValue, + options + ); + } else { + return setDoc(this._delegate, value as WithFieldValue); + } + } catch (e) { + throw replaceFunctionName(e, 'setDoc()', 'DocumentReference.set()'); + } + } + + update(value: PublicUpdateData): Promise; + update( + field: string | PublicFieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): Promise; + update( + fieldOrUpdateData: string | PublicFieldPath | PublicUpdateData, + value?: unknown, + ...moreFieldsAndValues: unknown[] + ): Promise { + try { + if (arguments.length === 1) { + return updateDoc(this._delegate, fieldOrUpdateData as PublicUpdateData); + } else { + return updateDoc( + this._delegate, + fieldOrUpdateData as string | ExpFieldPath, + value, + ...moreFieldsAndValues + ); + } + } catch (e) { + throw replaceFunctionName(e, 'updateDoc()', 'DocumentReference.update()'); + } + } + + delete(): Promise { + return deleteDoc(this._delegate); + } + + onSnapshot(observer: PartialObserver>): Unsubscribe; + onSnapshot( + options: PublicSnapshotListenOptions, + observer: PartialObserver> + ): Unsubscribe; + onSnapshot( + onNext: NextFn>, + onError?: ErrorFn, + onCompletion?: CompleteFn + ): Unsubscribe; + onSnapshot( + options: PublicSnapshotListenOptions, + onNext: NextFn>, + onError?: ErrorFn, + onCompletion?: CompleteFn + ): Unsubscribe; + + onSnapshot(...args: unknown[]): Unsubscribe { + const options = extractSnapshotOptions(args); + const observer = wrapObserver, ExpDocumentSnapshot>( + args, + result => + new DocumentSnapshot( + this.firestore, + new ExpDocumentSnapshot( + this.firestore._delegate, + this._userDataWriter, + result._key, + result._document, + result.metadata, + this._delegate.converter + ) + ) + ); + return onSnapshot(this._delegate, options, observer); + } + + get(options?: PublicGetOptions): Promise> { + let snap: Promise>; + if (options?.source === 'cache') { + snap = getDocFromCache(this._delegate); + } else if (options?.source === 'server') { + snap = getDocFromServer(this._delegate); + } else { + snap = getDoc(this._delegate); + } + + return snap.then( + result => + new DocumentSnapshot( + this.firestore, + new ExpDocumentSnapshot( + this.firestore._delegate, + this._userDataWriter, + result._key, + result._document, + result.metadata, + this._delegate.converter + ) + ) + ); + } + + withConverter(converter: null): PublicDocumentReference; + withConverter( + converter: PublicFirestoreDataConverter + ): PublicDocumentReference; + withConverter( + converter: PublicFirestoreDataConverter | null + ): PublicDocumentReference { + return new DocumentReference( + this.firestore, + converter + ? this._delegate.withConverter( + FirestoreDataConverter.getInstance(this.firestore, converter) + ) + : (this._delegate.withConverter(null) as ExpDocumentReference) + ); + } +} + +/** + * Replaces the function name in an error thrown by the firestore-exp API + * with the function names used in the classic API. + */ +function replaceFunctionName( + e: Error, + original: string | RegExp, + updated: string +): Error { + e.message = e.message.replace(original, updated); + return e; +} + +/** + * Iterates the list of arguments from an `onSnapshot` call and returns the + * first argument that may be an `SnapshotListenOptions` object. Returns an + * empty object if none is found. + */ +export function extractSnapshotOptions( + args: unknown[] +): PublicSnapshotListenOptions { + for (const arg of args) { + if (typeof arg === 'object' && !isPartialObserver(arg)) { + return arg as PublicSnapshotListenOptions; + } + } + return {}; +} + +/** + * Creates an observer that can be passed to the firestore-exp SDK. The + * observer converts all observed values into the format expected by the classic + * SDK. + * + * @param args - The list of arguments from an `onSnapshot` call. + * @param wrapper - The function that converts the firestore-exp type into the + * type used by this shim. + */ +export function wrapObserver( + args: unknown[], + wrapper: (val: ExpType) => CompatType +): PartialObserver { + let userObserver: PartialObserver; + if (isPartialObserver(args[0])) { + userObserver = args[0] as PartialObserver; + } else if (isPartialObserver(args[1])) { + userObserver = args[1]; + } else if (typeof args[0] === 'function') { + userObserver = { + next: args[0] as NextFn | undefined, + error: args[1] as ErrorFn | undefined, + complete: args[2] as CompleteFn | undefined + }; + } else { + userObserver = { + next: args[1] as NextFn | undefined, + error: args[2] as ErrorFn | undefined, + complete: args[3] as CompleteFn | undefined + }; + } + + return { + next: val => { + if (userObserver!.next) { + userObserver!.next(wrapper(val)); + } + }, + error: userObserver.error?.bind(userObserver), + complete: userObserver.complete?.bind(userObserver) + }; +} + +/** + * Options interface that can be provided to configure the deserialization of + * DocumentSnapshots. + */ +export interface SnapshotOptions extends PublicSnapshotOptions {} + +export class DocumentSnapshot + implements PublicDocumentSnapshot, Compat> +{ + constructor( + private readonly _firestore: Firestore, + readonly _delegate: ExpDocumentSnapshot + ) {} + + get ref(): DocumentReference { + return new DocumentReference(this._firestore, this._delegate.ref); + } + + get id(): string { + return this._delegate.id; + } + + get metadata(): SnapshotMetadata { + return this._delegate.metadata; + } + + get exists(): boolean { + return this._delegate.exists(); + } + + data(options?: PublicSnapshotOptions): T | undefined { + return this._delegate.data(options); + } + + get( + fieldPath: string | PublicFieldPath, + options?: PublicSnapshotOptions + // We are using `any` here to avoid an explicit cast by our users. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): any { + return this._delegate.get(fieldPath as string | ExpFieldPath, options); + } + + isEqual(other: DocumentSnapshot): boolean { + return snapshotEqual(this._delegate, other._delegate); + } +} + +export class QueryDocumentSnapshot + extends DocumentSnapshot + implements PublicQueryDocumentSnapshot +{ + data(options?: PublicSnapshotOptions): T { + const data = this._delegate.data(options); + _debugAssert( + data !== undefined, + 'Document in a QueryDocumentSnapshot should exist' + ); + return data; + } +} + +export class Query + implements PublicQuery, Compat> +{ + private readonly _userDataWriter: UserDataWriter; + + constructor(readonly firestore: Firestore, readonly _delegate: ExpQuery) { + this._userDataWriter = new UserDataWriter(firestore); + } + + where( + fieldPath: string | _FieldPath, + opStr: PublicWhereFilterOp, + value: unknown + ): Query { + try { + // The "as string" cast is a little bit of a hack. `where` accepts the + // FieldPath Compat type as input, but is not typed as such in order to + // not expose this via our public typings file. + return new Query( + this.firestore, + query(this._delegate, where(fieldPath as string, opStr, value)) + ); + } catch (e) { + throw replaceFunctionName(e, /(orderBy|where)\(\)/, 'Query.$1()'); + } + } + + orderBy( + fieldPath: string | _FieldPath, + directionStr?: PublicOrderByDirection + ): Query { + try { + // The "as string" cast is a little bit of a hack. `orderBy` accepts the + // FieldPath Compat type as input, but is not typed as such in order to + // not expose this via our public typings file. + return new Query( + this.firestore, + query(this._delegate, orderBy(fieldPath as string, directionStr)) + ); + } catch (e) { + throw replaceFunctionName(e, /(orderBy|where)\(\)/, 'Query.$1()'); + } + } + + limit(n: number): Query { + try { + return new Query(this.firestore, query(this._delegate, limit(n))); + } catch (e) { + throw replaceFunctionName(e, 'limit()', 'Query.limit()'); + } + } + + limitToLast(n: number): Query { + try { + return new Query( + this.firestore, + query(this._delegate, limitToLast(n)) + ); + } catch (e) { + throw replaceFunctionName(e, 'limitToLast()', 'Query.limitToLast()'); + } + } + + startAt(...args: any[]): Query { + try { + return new Query(this.firestore, query(this._delegate, startAt(...args))); + } catch (e) { + throw replaceFunctionName(e, 'startAt()', 'Query.startAt()'); + } + } + + startAfter(...args: any[]): Query { + try { + return new Query( + this.firestore, + query(this._delegate, startAfter(...args)) + ); + } catch (e) { + throw replaceFunctionName(e, 'startAfter()', 'Query.startAfter()'); + } + } + + endBefore(...args: any[]): Query { + try { + return new Query( + this.firestore, + query(this._delegate, endBefore(...args)) + ); + } catch (e) { + throw replaceFunctionName(e, 'endBefore()', 'Query.endBefore()'); + } + } + + endAt(...args: any[]): Query { + try { + return new Query(this.firestore, query(this._delegate, endAt(...args))); + } catch (e) { + throw replaceFunctionName(e, 'endAt()', 'Query.endAt()'); + } + } + + isEqual(other: PublicQuery): boolean { + return queryEqual(this._delegate, (other as Query)._delegate); + } + + get(options?: PublicGetOptions): Promise> { + let query: Promise>; + if (options?.source === 'cache') { + query = getDocsFromCache(this._delegate); + } else if (options?.source === 'server') { + query = getDocsFromServer(this._delegate); + } else { + query = getDocs(this._delegate); + } + return query.then( + result => + new QuerySnapshot( + this.firestore, + new ExpQuerySnapshot( + this.firestore._delegate, + this._userDataWriter, + this._delegate, + result._snapshot + ) + ) + ); + } + + onSnapshot(observer: PartialObserver>): Unsubscribe; + onSnapshot( + options: PublicSnapshotListenOptions, + observer: PartialObserver> + ): Unsubscribe; + onSnapshot( + onNext: NextFn>, + onError?: ErrorFn, + onCompletion?: CompleteFn + ): Unsubscribe; + onSnapshot( + options: PublicSnapshotListenOptions, + onNext: NextFn>, + onError?: ErrorFn, + onCompletion?: CompleteFn + ): Unsubscribe; + + onSnapshot(...args: unknown[]): Unsubscribe { + const options = extractSnapshotOptions(args); + const observer = wrapObserver, ExpQuerySnapshot>( + args, + snap => + new QuerySnapshot( + this.firestore, + new ExpQuerySnapshot( + this.firestore._delegate, + this._userDataWriter, + this._delegate, + snap._snapshot + ) + ) + ); + return onSnapshot(this._delegate, options, observer); + } + + withConverter(converter: null): Query; + withConverter(converter: PublicFirestoreDataConverter): Query; + withConverter( + converter: PublicFirestoreDataConverter | null + ): Query { + return new Query( + this.firestore, + converter + ? this._delegate.withConverter( + FirestoreDataConverter.getInstance(this.firestore, converter) + ) + : (this._delegate.withConverter(null) as ExpQuery) + ); + } +} + +export class DocumentChange + implements PublicDocumentChange, Compat> +{ + constructor( + private readonly _firestore: Firestore, + readonly _delegate: ExpDocumentChange + ) {} + + get type(): PublicDocumentChangeType { + return this._delegate.type; + } + + get doc(): QueryDocumentSnapshot { + return new QueryDocumentSnapshot(this._firestore, this._delegate.doc); + } + + get oldIndex(): number { + return this._delegate.oldIndex; + } + + get newIndex(): number { + return this._delegate.newIndex; + } +} + +export class QuerySnapshot + implements PublicQuerySnapshot, Compat> +{ + constructor( + readonly _firestore: Firestore, + readonly _delegate: ExpQuerySnapshot + ) {} + + get query(): Query { + return new Query(this._firestore, this._delegate.query); + } + + get metadata(): SnapshotMetadata { + return this._delegate.metadata; + } + + get size(): number { + return this._delegate.size; + } + + get empty(): boolean { + return this._delegate.empty; + } + + get docs(): Array> { + return this._delegate.docs.map( + doc => new QueryDocumentSnapshot(this._firestore, doc) + ); + } + + docChanges( + options?: PublicSnapshotListenOptions + ): Array> { + return this._delegate + .docChanges(options) + .map(docChange => new DocumentChange(this._firestore, docChange)); + } + + forEach( + callback: (result: QueryDocumentSnapshot) => void, + thisArg?: unknown + ): void { + this._delegate.forEach(snapshot => { + callback.call( + thisArg, + new QueryDocumentSnapshot(this._firestore, snapshot) + ); + }); + } + + isEqual(other: QuerySnapshot): boolean { + return snapshotEqual(this._delegate, other._delegate); + } +} + +export class CollectionReference + extends Query + implements PublicCollectionReference +{ + constructor( + readonly firestore: Firestore, + readonly _delegate: ExpCollectionReference + ) { + super(firestore, _delegate); + } + + get id(): string { + return this._delegate.id; + } + + get path(): string { + return this._delegate.path; + } + + get parent(): DocumentReference | null { + const docRef = this._delegate.parent; + return docRef ? new DocumentReference(this.firestore, docRef) : null; + } + + doc(documentPath?: string): DocumentReference { + try { + if (documentPath === undefined) { + // Call `doc` without `documentPath` if `documentPath` is `undefined` + // as `doc` validates the number of arguments to prevent users from + // accidentally passing `undefined`. + return new DocumentReference(this.firestore, doc(this._delegate)); + } else { + return new DocumentReference( + this.firestore, + doc(this._delegate, documentPath) + ); + } + } catch (e) { + throw replaceFunctionName(e, 'doc()', 'CollectionReference.doc()'); + } + } + + add(data: T): Promise> { + return addDoc(this._delegate, data as WithFieldValue).then( + docRef => new DocumentReference(this.firestore, docRef) + ); + } + + isEqual(other: CollectionReference): boolean { + return refEqual(this._delegate, other._delegate); + } + + withConverter(converter: null): CollectionReference; + withConverter( + converter: PublicFirestoreDataConverter + ): CollectionReference; + withConverter( + converter: PublicFirestoreDataConverter | null + ): CollectionReference { + return new CollectionReference( + this.firestore, + converter + ? this._delegate.withConverter( + FirestoreDataConverter.getInstance(this.firestore, converter) + ) + : (this._delegate.withConverter(null) as ExpCollectionReference) + ); + } +} + +function castReference( + documentRef: PublicDocumentReference +): ExpDocumentReference { + return _cast>(documentRef, ExpDocumentReference); +} diff --git a/packages/firestore-compat/src/api/field_path.ts b/packages/firestore-compat/src/api/field_path.ts new file mode 100644 index 00000000000..a236077d496 --- /dev/null +++ b/packages/firestore-compat/src/api/field_path.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FieldPath as ExpFieldPath, + _FieldPath as InternalFieldPath +} from '@firebase/firestore'; +import { FieldPath as PublicFieldPath } from '@firebase/firestore-types'; +import { Compat, getModularInstance } from '@firebase/util'; + +// The objects that are a part of this API are exposed to third-parties as +// compiled javascript so we want to flag our private members with a leading +// underscore to discourage their use. + +/** + * A `FieldPath` refers to a field in a document. The path may consist of a + * single field name (referring to a top-level field in the document), or a list + * of field names (referring to a nested field in the document). + */ +export class FieldPath implements PublicFieldPath, Compat { + readonly _delegate: ExpFieldPath; + /** + * Creates a FieldPath from the provided field names. If more than one field + * name is provided, the path will point to a nested field in a document. + * + * @param fieldNames - A list of field names. + */ + constructor(...fieldNames: string[]) { + this._delegate = new ExpFieldPath(...fieldNames); + } + + static documentId(): FieldPath { + /** + * Internal Note: The backend doesn't technically support querying by + * document ID. Instead it queries by the entire document name (full path + * included), but in the cases we currently support documentId(), the net + * effect is the same. + */ + return new FieldPath(InternalFieldPath.keyField().canonicalString()); + } + + isEqual(other: PublicFieldPath): boolean { + other = getModularInstance(other); + + if (!(other instanceof ExpFieldPath)) { + return false; + } + return this._delegate._internalPath.isEqual(other._internalPath); + } +} diff --git a/packages/firestore-compat/src/api/field_value.ts b/packages/firestore-compat/src/api/field_value.ts new file mode 100644 index 00000000000..25cb4c81242 --- /dev/null +++ b/packages/firestore-compat/src/api/field_value.ts @@ -0,0 +1,65 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + arrayRemove, + arrayUnion, + deleteField, + FieldValue as FieldValue1, + increment, + serverTimestamp +} from '@firebase/firestore'; +import { FieldValue as PublicFieldValue } from '@firebase/firestore-types'; +import { Compat } from '@firebase/util'; + +export class FieldValue implements PublicFieldValue, Compat { + static serverTimestamp(): FieldValue { + const delegate = serverTimestamp(); + delegate._methodName = 'FieldValue.serverTimestamp'; + return new FieldValue(delegate); + } + + static delete(): FieldValue { + const delegate = deleteField(); + delegate._methodName = 'FieldValue.delete'; + return new FieldValue(delegate); + } + + static arrayUnion(...elements: unknown[]): FieldValue { + const delegate = arrayUnion(...elements); + delegate._methodName = 'FieldValue.arrayUnion'; + return new FieldValue(delegate); + } + + static arrayRemove(...elements: unknown[]): FieldValue { + const delegate = arrayRemove(...elements); + delegate._methodName = 'FieldValue.arrayRemove'; + return new FieldValue(delegate); + } + + static increment(n: number): FieldValue { + const delegate = increment(n); + delegate._methodName = 'FieldValue.increment'; + return new FieldValue(delegate); + } + + constructor(readonly _delegate: FieldValue1) {} + + isEqual(other: FieldValue): boolean { + return this._delegate.isEqual(other._delegate); + } +} diff --git a/packages/firestore/src/exp/geo_point.ts b/packages/firestore-compat/src/api/geo_point.ts similarity index 88% rename from packages/firestore/src/exp/geo_point.ts rename to packages/firestore-compat/src/api/geo_point.ts index ffcd008a807..2831e048330 100644 --- a/packages/firestore/src/exp/geo_point.ts +++ b/packages/firestore-compat/src/api/geo_point.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2020 Google LLC + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -export { GeoPoint } from '../lite/geo_point'; +export { GeoPoint } from '@firebase/firestore'; diff --git a/packages/firestore-compat/src/api/observer.ts b/packages/firestore-compat/src/api/observer.ts new file mode 100644 index 00000000000..93218010028 --- /dev/null +++ b/packages/firestore-compat/src/api/observer.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirestoreError } from '@firebase/firestore'; + +/** + * Observer/Subscribe interfaces. + */ +export type NextFn = (value: T) => void; +export type ErrorFn = (error: FirestoreError) => void; +export type CompleteFn = () => void; + +// Allow for any of the Observer methods to be undefined. +export interface PartialObserver { + next?: NextFn; + error?: ErrorFn; + complete?: CompleteFn; +} + +export function isPartialObserver(obj: unknown): obj is PartialObserver { + return implementsAnyMethods(obj, ['next', 'error', 'complete']); +} + +/** + * Returns true if obj is an object and contains at least one of the specified + * methods. + */ +function implementsAnyMethods(obj: unknown, methods: string[]): boolean { + if (typeof obj !== 'object' || obj === null) { + return false; + } + + const object = obj as Record; + for (const method of methods) { + if (method in object && typeof object[method] === 'function') { + return true; + } + } + return false; +} diff --git a/packages/firestore/src/exp/timestamp.ts b/packages/firestore-compat/src/api/timestamp.ts similarity index 88% rename from packages/firestore/src/exp/timestamp.ts rename to packages/firestore-compat/src/api/timestamp.ts index f0de0029048..e983fbbe466 100644 --- a/packages/firestore/src/exp/timestamp.ts +++ b/packages/firestore-compat/src/api/timestamp.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2020 Google LLC + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -export { Timestamp } from '../lite/timestamp'; +export { Timestamp } from '@firebase/firestore'; diff --git a/packages/firestore/compat/config.ts b/packages/firestore-compat/src/config.ts similarity index 80% rename from packages/firestore/compat/config.ts rename to packages/firestore-compat/src/config.ts index 295ecdc8ecc..29dea55d0a8 100644 --- a/packages/firestore/compat/config.ts +++ b/packages/firestore-compat/src/config.ts @@ -20,14 +20,14 @@ import { FirebaseApp } from '@firebase/app-compat'; import { FirebaseNamespace } from '@firebase/app-types'; import { _FirebaseNamespace } from '@firebase/app-types/private'; import { Component, ComponentType } from '@firebase/component'; - import { - Firestore as FirebaseFirestore, + Firestore as ModularFirestore, CACHE_SIZE_UNLIMITED, GeoPoint, Timestamp -} from '../exp/index'; // import from the exp public API -import { Blob } from '../src/api/blob'; +} from '@firebase/firestore'; // import from the exp public API + +import { Blob } from './api/blob'; import { Firestore, Transaction, @@ -39,9 +39,9 @@ import { QuerySnapshot, WriteBatch, setLogLevel -} from '../src/api/database'; -import { FieldPath } from '../src/api/field_path'; -import { FieldValue } from '../src/api/field_value'; +} from './api/database'; +import { FieldPath } from './api/field_path'; +import { FieldValue } from './api/field_value'; const firestoreNamespace = { Firestore, @@ -62,12 +62,6 @@ const firestoreNamespace = { CACHE_SIZE_UNLIMITED }; -declare module '@firebase/component' { - interface NameServiceMapping { - 'firestore-compat': Firestore; - } -} - /** * Configures Firestore as part of the Firebase SDK by calling registerComponent. * @@ -79,7 +73,7 @@ export function configureForFirebase( firebase: FirebaseNamespace, firestoreFactory: ( app: FirebaseApp, - firestoreExp: FirebaseFirestore + firestoreExp: ModularFirestore ) => Firestore ): void { (firebase as _FirebaseNamespace).INTERNAL.registerComponent( @@ -87,9 +81,7 @@ export function configureForFirebase( 'firestore-compat', container => { const app = container.getProvider('app-compat').getImmediate()!; - const firestoreExp = container - .getProvider('firestore-exp') - .getImmediate()!; + const firestoreExp = container.getProvider('firestore').getImmediate()!; return firestoreFactory(app, firestoreExp); }, ComponentType.PUBLIC diff --git a/packages/firestore/index.console.ts b/packages/firestore-compat/src/index.console.ts similarity index 72% rename from packages/firestore/index.console.ts rename to packages/firestore-compat/src/index.console.ts index cf2c827ad5d..8edf89aa317 100644 --- a/packages/firestore/index.console.ts +++ b/packages/firestore-compat/src/index.console.ts @@ -17,26 +17,29 @@ import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; +import { + _DatabaseId, + Firestore as FirestoreExp, + FirestoreError +} from '@firebase/firestore'; import { Firestore as FirestoreCompat, MemoryPersistenceProvider -} from './src/api/database'; -import { DatabaseId } from './src/core/database_info'; -import { Firestore as FirestoreExp } from './src/exp/database'; -import { Code, FirestoreError } from './src/util/error'; +} from './api/database'; +import { EmptyCredentialsProvider } from './src/api/credentials'; + export { CollectionReference, DocumentReference, DocumentSnapshot, QuerySnapshot -} from './src/api/database'; - -export { Blob } from './src/api/blob'; -export { GeoPoint } from './src/api/geo_point'; -export { FieldPath } from './src/api/field_path'; -export { FieldValue } from './src/api/field_value'; -export { Timestamp } from './src/api/timestamp'; +} from './api/database'; +export { Blob } from './api/blob'; +export { GeoPoint } from './api/geo_point'; +export { FieldPath } from './api/field_path'; +export { FieldValue } from './api/field_value'; +export { Timestamp } from './api/timestamp'; export interface FirestoreDatabase { projectId: string; @@ -53,7 +56,7 @@ export class Firestore extends FirestoreCompat { databaseIdFromFirestoreDatabase(firestoreDatabase), new FirestoreExp( databaseIdFromFirestoreDatabase(firestoreDatabase), - authProvider + new EmptyCredentialsProvider() ), new MemoryPersistenceProvider() ); @@ -62,11 +65,11 @@ export class Firestore extends FirestoreCompat { function databaseIdFromFirestoreDatabase( firestoreDatabase: FirestoreDatabase -): DatabaseId { +): _DatabaseId { if (!firestoreDatabase.projectId) { - throw new FirestoreError(Code.INVALID_ARGUMENT, 'Must provide projectId'); + throw new FirestoreError('invalid-argument', 'Must provide projectId'); } - return new DatabaseId( + return new _DatabaseId( firestoreDatabase.projectId, firestoreDatabase.database ); diff --git a/packages/firestore/compat/index.node.ts b/packages/firestore-compat/src/index.node.ts similarity index 78% rename from packages/firestore/compat/index.node.ts rename to packages/firestore-compat/src/index.node.ts index 21ecd90dbf1..38c7954bf8c 100644 --- a/packages/firestore/compat/index.node.ts +++ b/packages/firestore-compat/src/index.node.ts @@ -19,19 +19,16 @@ import firebase from '@firebase/app-compat'; import { FirebaseNamespace } from '@firebase/app-types'; -import { Firestore, IndexedDbPersistenceProvider } from '../src/api/database'; -import { setSDKVersion } from '../src/core/version'; +import { name, version } from '../package.json'; -import { registerBundle } from './bundle'; +import { Firestore, IndexedDbPersistenceProvider } from './api/database'; import { configureForFirebase } from './config'; -import { name, version } from './package.json'; /** * Registers the main Firestore Node build with the components framework. * Persistence can be enabled via `firebase.firestore().enablePersistence()`. */ export function registerFirestore(instance: FirebaseNamespace): void { - setSDKVersion(instance.SDK_VERSION); configureForFirebase( instance, (app, firestoreExp) => @@ -40,5 +37,4 @@ export function registerFirestore(instance: FirebaseNamespace): void { instance.registerVersion(name, version, 'node'); } -registerFirestore((firebase as unknown) as FirebaseNamespace); -registerBundle(Firestore); +registerFirestore(firebase as unknown as FirebaseNamespace); diff --git a/packages/firestore/compat/index.rn.ts b/packages/firestore-compat/src/index.rn.ts similarity index 78% rename from packages/firestore/compat/index.rn.ts rename to packages/firestore-compat/src/index.rn.ts index 32f2e0dab4a..6b28e6c5e94 100644 --- a/packages/firestore/compat/index.rn.ts +++ b/packages/firestore-compat/src/index.rn.ts @@ -19,18 +19,15 @@ import firebase from '@firebase/app-compat'; import { FirebaseNamespace } from '@firebase/app-types'; -import { Firestore, IndexedDbPersistenceProvider } from '../src/api/database'; -import { setSDKVersion } from '../src/core/version'; +import { name, version } from '../package.json'; -import { registerBundle } from './bundle'; +import { Firestore, IndexedDbPersistenceProvider } from './api/database'; import { configureForFirebase } from './config'; -import { name, version } from './package.json'; /** * Registers the main Firestore ReactNative build with the components framework. * Persistence can be enabled via `firebase.firestore().enablePersistence()`. */ export function registerFirestore(instance: FirebaseNamespace): void { - setSDKVersion(instance.SDK_VERSION); configureForFirebase( instance, (app, firestoreExp) => @@ -39,5 +36,4 @@ export function registerFirestore(instance: FirebaseNamespace): void { instance.registerVersion(name, version, 'rn'); } -registerFirestore((firebase as unknown) as FirebaseNamespace); -registerBundle(Firestore); +registerFirestore(firebase as unknown as FirebaseNamespace); diff --git a/packages/firestore/compat/index.ts b/packages/firestore-compat/src/index.ts similarity index 85% rename from packages/firestore/compat/index.ts rename to packages/firestore-compat/src/index.ts index 9084fe34150..5b1eedd5524 100644 --- a/packages/firestore/compat/index.ts +++ b/packages/firestore-compat/src/index.ts @@ -20,21 +20,18 @@ import firebase from '@firebase/app-compat'; import { FirebaseNamespace } from '@firebase/app-types'; import * as types from '@firebase/firestore-types'; -import { Firestore, IndexedDbPersistenceProvider } from '../src/api/database'; -import { setSDKVersion } from '../src/core/version'; +import { name, version } from '../package.json'; -import { registerBundle } from './bundle'; +import { Firestore, IndexedDbPersistenceProvider } from './api/database'; import { configureForFirebase } from './config'; -import { name, version } from './package.json'; -import '../register-module'; +import './register-module'; /** * Registers the main Firestore build with the components framework. * Persistence can be enabled via `firebase.firestore().enablePersistence()`. */ export function registerFirestore(instance: FirebaseNamespace): void { - setSDKVersion(instance.SDK_VERSION); configureForFirebase( instance, (app, firestoreExp) => @@ -43,8 +40,7 @@ export function registerFirestore(instance: FirebaseNamespace): void { instance.registerVersion(name, version); } -registerFirestore((firebase as unknown) as FirebaseNamespace); -registerBundle(Firestore); +registerFirestore(firebase as unknown as FirebaseNamespace); declare module '@firebase/app-compat' { interface FirebaseNamespace { diff --git a/packages/firestore/register-module.ts b/packages/firestore-compat/src/register-module.ts similarity index 97% rename from packages/firestore/register-module.ts rename to packages/firestore-compat/src/register-module.ts index f76fabd5a1d..7bf6e087ce4 100644 --- a/packages/firestore/register-module.ts +++ b/packages/firestore-compat/src/register-module.ts @@ -17,7 +17,7 @@ import * as types from '@firebase/firestore-types'; -declare module '@firebase/app-types' { +declare module '@firebase/app-compat' { interface FirebaseNamespace { firestore: { (app?: FirebaseApp): types.FirebaseFirestore; diff --git a/packages-exp/app-check-exp/karma.conf.js b/packages/firestore-compat/src/util/input_validation.ts similarity index 51% rename from packages-exp/app-check-exp/karma.conf.js rename to packages/firestore-compat/src/util/input_validation.ts index 324777bcd54..8a60a6955f9 100644 --- a/packages-exp/app-check-exp/karma.conf.js +++ b/packages/firestore-compat/src/util/input_validation.ts @@ -15,18 +15,26 @@ * limitations under the License. */ -// eslint-disable-next-line @typescript-eslint/no-require-imports -const karmaBase = require('../../config/karma.base'); +import { FirestoreError } from '@firebase/firestore'; +import { SetOptions } from '@firebase/firestore-types'; -const files = [`**/*.test.ts`]; +export function validateSetOptions( + methodName: string, + options: SetOptions | undefined +): SetOptions { + if (options === undefined) { + return { + merge: false + }; + } -module.exports = function (config) { - config.set({ - ...karmaBase, - files, - preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, - frameworks: ['mocha'] - }); -}; + if (options.mergeFields !== undefined && options.merge !== undefined) { + throw new FirestoreError( + 'invalid-argument', + `Invalid options passed to function ${methodName}(): You cannot ` + + 'specify both "merge" and "mergeFields".' + ); + } -module.exports.files = files; + return options; +} diff --git a/packages/firestore/tools/console.build.js b/packages/firestore-compat/tools/console.build.js similarity index 56% rename from packages/firestore/tools/console.build.js rename to packages/firestore-compat/tools/console.build.js index 2a7e6de38c1..706cc19438e 100644 --- a/packages/firestore/tools/console.build.js +++ b/packages/firestore-compat/tools/console.build.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,49 +22,37 @@ const rollup = require('rollup'); const { uglify } = require('rollup-plugin-uglify'); const { nodeResolve } = require('@rollup/plugin-node-resolve'); +const typescriptPlugin = require('rollup-plugin-typescript2'); +const typescript = require('typescript'); +const json = require('@rollup/plugin-json'); const fs = require('fs'); const util = require('util'); const fs_writeFile = util.promisify(fs.writeFile); - -const rollupUtil = require('../rollup.shared'); +const rollupUtil = require('../../firestore/rollup.shared'); const EXPORTNAME = '__firestore_exports__'; +const OUTPUT_FOLDER = 'dist'; +const OUTPUT_FILE = 'standalone.js'; -const esm2017OutputFile = 'dist/standalone.esm2017.js'; -const esm5OutputFile = 'dist/standalone.js'; - -const es2017InputOptions = { - input: 'index.console.ts', - plugins: rollupUtil.es2017Plugins('browser', /* mangled= */ true), - external: rollupUtil.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } -}; - -const es2017OutputOptions = { - file: esm2017OutputFile, - format: 'es' -}; - -const es2017toEs5InputOptions = { - input: esm2017OutputFile, +const es5InputOptions = { + input: 'src/index.console.ts', plugins: [ nodeResolve(), - ...rollupUtil.es2017ToEs5Plugins(/* mangled= */ true), + typescriptPlugin({ + typescript, + transformers: [rollupUtil.removeAssertTransformer] + }), + json({ preferConst: true }), uglify({ output: { ascii_only: true // escape unicode chars } }) - ], - treeshake: { - moduleSideEffects: false - } -}; + ] +} -const es2017toEs5OutputOptions = { - file: esm5OutputFile, +const es5OutputOptions = { + file: `${OUTPUT_FOLDER}/${OUTPUT_FILE}`, name: EXPORTNAME, format: 'iife' }; @@ -76,18 +64,18 @@ exports = eval(`; const POSTFIX = ` + '${EXPORTNAME};');`; async function build() { - // Create an ES2017 bundle - const es2017Bundle = await rollup.rollup(es2017InputOptions); - await es2017Bundle.write(es2017OutputOptions); - - // Transpile down to ES5 - const es5Bundle = await rollup.rollup(es2017toEs5InputOptions); + const es5Bundle = await rollup.rollup(es5InputOptions); const { output: [{ code }] - } = await es5Bundle.generate(es2017toEs5OutputOptions); + } = await es5Bundle.generate(es5OutputOptions); const output = `${PREFIX}${JSON.stringify(String(code))}${POSTFIX}`; - await fs_writeFile(es2017toEs5OutputOptions.file, output, 'utf-8'); + + if (!fs.existsSync(OUTPUT_FOLDER)) { + fs.mkdirSync(OUTPUT_FOLDER); + } + + await fs_writeFile(es5OutputOptions.file, output, 'utf-8'); } build(); diff --git a/packages/polyfill/tsconfig.json b/packages/firestore-compat/tsconfig.json similarity index 100% rename from packages/polyfill/tsconfig.json rename to packages/firestore-compat/tsconfig.json diff --git a/packages/firestore-types/index.d.ts b/packages/firestore-types/index.d.ts index 7085d4eaef7..d198fd78866 100644 --- a/packages/firestore-types/index.d.ts +++ b/packages/firestore-types/index.d.ts @@ -507,6 +507,6 @@ export interface FirestoreError { declare module '@firebase/component' { interface NameServiceMapping { - 'firestore': FirebaseFirestore; + 'firestore-compat': FirebaseFirestore; } } diff --git a/packages/firestore/.eslintrc.js b/packages/firestore/.eslintrc.js index 4d43fc80013..5dd443333d9 100644 --- a/packages/firestore/.eslintrc.js +++ b/packages/firestore/.eslintrc.js @@ -24,6 +24,7 @@ module.exports = { tsconfigRootDir: __dirname }, plugins: ['import'], + ignorePatterns: ['compat/*'], rules: { 'no-console': ['error', { allow: ['warn', 'error'] }], '@typescript-eslint/no-unused-vars': [ @@ -82,20 +83,6 @@ module.exports = { 'import/no-extraneous-dependencies': 'off', '@typescript-eslint/no-require-imports': 'off' } - }, - // TODO(firestorelite): Remove this exception when app-exp is published - { - files: ['lite/**/*.ts'], - rules: { - 'import/no-extraneous-dependencies': 'off' - } - }, - // TODO(firestoreexp): Remove this exception when app-exp is published - { - files: ['exp/**/*.ts'], - rules: { - 'import/no-extraneous-dependencies': 'off' - } } ] }; diff --git a/packages/firestore/.gitignore b/packages/firestore/.gitignore new file mode 100644 index 00000000000..cb7b960ce00 --- /dev/null +++ b/packages/firestore/.gitignore @@ -0,0 +1 @@ +/compat \ No newline at end of file diff --git a/packages/firestore/.idea/runConfigurations/All_Tests__Emulator_.xml b/packages/firestore/.idea/runConfigurations/All_Tests__Emulator_.xml index 92d7cc57670..86a66f0f39e 100644 --- a/packages/firestore/.idea/runConfigurations/All_Tests__Emulator_.xml +++ b/packages/firestore/.idea/runConfigurations/All_Tests__Emulator_.xml @@ -11,9 +11,9 @@ bdd - --require ts-node/register/type-check --require index.node.ts --timeout 5000 + --require ts-node/register/type-check --require test/register.ts --timeout 5000 PATTERN test/{,!(browser|lite)/**/}*.test.ts - \ No newline at end of file + diff --git a/packages/firestore/.idea/runConfigurations/All_Tests__Emulator_w__Mock_Persistence_.xml b/packages/firestore/.idea/runConfigurations/All_Tests__Emulator_w__Mock_Persistence_.xml index c2181e91517..5dcd1e701c2 100644 --- a/packages/firestore/.idea/runConfigurations/All_Tests__Emulator_w__Mock_Persistence_.xml +++ b/packages/firestore/.idea/runConfigurations/All_Tests__Emulator_w__Mock_Persistence_.xml @@ -12,9 +12,9 @@ bdd - --require ts-node/register/type-check --require index.node.ts --require test/util/node_persistence.ts --timeout 5000 + --require ts-node/register/type-check --require test/register.ts --require test/util/node_persistence.ts --timeout 5000 PATTERN test/{,!(browser|lite)/**/}*.test.ts - \ No newline at end of file + diff --git a/packages/firestore/.idea/runConfigurations/Integration_Tests__Emulator_.xml b/packages/firestore/.idea/runConfigurations/Integration_Tests__Emulator_.xml index 1ea460ca6e5..421038182f0 100644 --- a/packages/firestore/.idea/runConfigurations/Integration_Tests__Emulator_.xml +++ b/packages/firestore/.idea/runConfigurations/Integration_Tests__Emulator_.xml @@ -11,9 +11,9 @@ bdd - --require ts-node/register/type-check --require index.node.ts --timeout 5000 + --require ts-node/register/type-check --require test/register.ts --require compat/index.node.ts --timeout 5000 PATTERN test/integration/{,!(browser|lite)/**/}*.test.ts - \ No newline at end of file + diff --git a/packages/firestore/.idea/runConfigurations/Integration_Tests__Emulator_w__Mock_Persistence_.xml b/packages/firestore/.idea/runConfigurations/Integration_Tests__Emulator_w__Mock_Persistence_.xml index 6d9215345e1..828749694c3 100644 --- a/packages/firestore/.idea/runConfigurations/Integration_Tests__Emulator_w__Mock_Persistence_.xml +++ b/packages/firestore/.idea/runConfigurations/Integration_Tests__Emulator_w__Mock_Persistence_.xml @@ -12,9 +12,9 @@ bdd - --require ts-node/register/type-check --require index.node.ts --require test/util/node_persistence.ts --timeout 5000 + --require ts-node/register/type-check --require test/register.ts --require test/util/node_persistence.ts --timeout 5000 PATTERN test/integration/{,!(browser|lite)/**/}*.test.ts - \ No newline at end of file + diff --git a/packages/firestore/.idea/runConfigurations/Unit_Tests.xml b/packages/firestore/.idea/runConfigurations/Unit_Tests.xml index 5d0dcda754a..4c525bfb4fa 100644 --- a/packages/firestore/.idea/runConfigurations/Unit_Tests.xml +++ b/packages/firestore/.idea/runConfigurations/Unit_Tests.xml @@ -9,9 +9,9 @@ bdd - --require ts-node/register/type-check --require index.node.ts + --require ts-node/register/type-check --require test/register.ts PATTERN test/unit/{,!(browser|lite)/**/}*.test.ts - \ No newline at end of file + diff --git a/packages/firestore/.idea/runConfigurations/Unit_Tests__w__Mock_Persistence_.xml b/packages/firestore/.idea/runConfigurations/Unit_Tests__w__Mock_Persistence_.xml index f62d34dcd93..759a10bfda1 100644 --- a/packages/firestore/.idea/runConfigurations/Unit_Tests__w__Mock_Persistence_.xml +++ b/packages/firestore/.idea/runConfigurations/Unit_Tests__w__Mock_Persistence_.xml @@ -10,9 +10,9 @@ bdd - --require ts-node/register/type-check --require index.node.ts --require test/util/node_persistence.ts + --require ts-node/register/type-check --require test/register.ts --require test/util/node_persistence.ts PATTERN test/unit/{,!(browser|lite)/**/}*.test.ts - \ No newline at end of file + diff --git a/packages/firestore/api-extractor.json b/packages/firestore/api-extractor.json index a81e7c54b52..ed10a0d62ac 100644 --- a/packages/firestore/api-extractor.json +++ b/packages/firestore/api-extractor.json @@ -5,7 +5,7 @@ */ "extends": "../../config/api-extractor.json", // Point it to your entry point d.ts file. - "mainEntryPointFilePath": "/dist/exp/index.d.ts", + "mainEntryPointFilePath": "/dist/index.d.ts", "additionalEntryPoints": [ { "modulePath": "lite", diff --git a/packages/firestore/bundle/package.json b/packages/firestore/bundle/package.json deleted file mode 100644 index df3c12d9f3f..00000000000 --- a/packages/firestore/bundle/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@firebase/firestore/bundle", - "description": "Firestore bundle", - "main": "../dist/node-cjs/bundle.js", - "main-esm2017": "../dist/node-esm2017/bundle.js", - "react-native": "../dist/rn/bundle.js", - "browser": "../dist/esm5/bundle.js", - "module": "../dist/esm5/bundle.js", - "esm2017": "../dist/esm2017/bundle.js" -} diff --git a/packages/firestore/compat/bundle.ts b/packages/firestore/compat/bundle.ts deleted file mode 100644 index 427c2e50680..00000000000 --- a/packages/firestore/compat/bundle.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - loadBundle as expLoadBundle, - namedQuery as expNamedQuery, - LoadBundleTask -} from '../exp/index'; -import { Firestore, Query } from '../src/api/database'; - -export function loadBundle( - this: Firestore, - data: ArrayBuffer | ReadableStream | string -): LoadBundleTask { - return expLoadBundle(this._delegate, data); -} - -export function namedQuery( - this: Firestore, - queryName: string -): Promise { - return expNamedQuery(this._delegate, queryName).then(expQuery => { - if (!expQuery) { - return null; - } - return new Query( - this, - // We can pass `expQuery` here directly since named queries don't have a UserDataConverter. - // Otherwise, we would have to create a new ExpQuery and pass the old UserDataConverter. - expQuery - ); - }); -} - -/** - * Prototype patches bundle loading to Firestore. - */ -export function registerBundle(instance: typeof Firestore): void { - instance.prototype.loadBundle = loadBundle; - instance.prototype.namedQuery = namedQuery; -} diff --git a/packages/firestore/compat/package.json b/packages/firestore/compat/package.json deleted file mode 100644 index 180edcd7f0c..00000000000 --- a/packages/firestore/compat/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@firebase/firestore-compat", - "version": "0.0.900", - "description": "The Cloud Firestore component of the Firebase JS SDK.", - "author": "Firebase (https://firebase.google.com/)", - "main": "../dist/compat/node-cjs/index.js", - "main-esm2017": "../dist/compat/node-esm2017/index.js", - "react-native": "../dist/compat/rn/index.js", - "browser": "../dist/compat/esm2017/index.js", - "module": "../dist/compat/esm2017/index.js", - "esm5": "../dist/compat/esm5/index.js", - "license": "Apache-2.0", - "typings": "../dist/compat/esm2017/firestore/compat/index.d.ts" - } - \ No newline at end of file diff --git a/packages/firestore/exp/package.json b/packages/firestore/exp/package.json deleted file mode 100644 index 375bed2ae15..00000000000 --- a/packages/firestore/exp/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@firebase/firestore-exp", - "description": "A tree-shakeable version of the Firestore SDK", - "main": "../dist/exp/index.node.cjs.js", - "main-esm": "../dist/exp/index.node.esm2017.js", - "module": "../dist/exp/index.browser.esm2017.js", - "browser": "../dist/exp/index.browser.esm2017.js", - "react-native": "../dist/exp/index.rn.esm2017.js", - "esm5": "../dist/exp/index.browser.esm5.js", - "typings": "../dist/exp/index.d.ts", - "private": true -} diff --git a/packages/firestore/export.ts b/packages/firestore/export.ts deleted file mode 100644 index 4e97cd52b8d..00000000000 --- a/packages/firestore/export.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Firestore, Query } from './src/api/database'; -import { LoadBundleTask } from './src/exp/bundle'; -import { - loadBundle as expLoadBundle, - namedQuery as expNamedQuery -} from './src/exp/database'; - -export { Blob } from './src/api/blob'; -export { - CollectionReference, - DocumentReference, - DocumentSnapshot, - Firestore, - Query, - QueryDocumentSnapshot, - QuerySnapshot, - IndexedDbPersistenceProvider, - MemoryPersistenceProvider, - Transaction, - WriteBatch, - setLogLevel -} from './src/api/database'; -export { CACHE_SIZE_UNLIMITED } from './src/exp/database'; -export { GeoPoint } from './src/api/geo_point'; -export { FieldPath } from './src/api/field_path'; -export { FieldValue } from './src/api/field_value'; -export { Timestamp } from './src/api/timestamp'; -export { Firestore as ExpFirestore } from './src/exp/database'; - -export function loadBundle( - this: Firestore, - data: ArrayBuffer | ReadableStream | string -): LoadBundleTask { - return expLoadBundle(this._delegate, data); -} - -export function namedQuery( - this: Firestore, - queryName: string -): Promise { - return expNamedQuery(this._delegate, queryName).then(expQuery => { - if (!expQuery) { - return null; - } - return new Query( - this, - // We can pass `expQuery` here directly since named queries don't have a UserDataConverter. - // Otherwise, we would have to create a new ExpQuery and pass the old UserDataConverter. - expQuery - ); - }); -} diff --git a/packages/firestore/externs.json b/packages/firestore/externs.json index 5b9cb8cda84..fbbdae36636 100644 --- a/packages/firestore/externs.json +++ b/packages/firestore/externs.json @@ -14,12 +14,12 @@ "node_modules/typescript/lib/lib.es2017.string.d.ts", "packages/app-types/index.d.ts", "packages/app-types/private.d.ts", - "packages-exp/app-exp/dist/app-exp.d.ts", + "packages/app/dist/app.d.ts", "packages/auth-interop-types/index.d.ts", "packages/firestore/dist/lite/internal.d.ts", - "packages/firestore/dist/exp/internal.d.ts", + "packages/firestore/dist/internal.d.ts", "packages/firestore-types/index.d.ts", - "packages/firebase/index.d.ts", + "packages/firebase/compat/index.d.ts", "packages/component/dist/src/component.d.ts", "packages/component/dist/src/provider.d.ts", "packages/component/dist/src/component_container.d.ts", @@ -29,7 +29,7 @@ "packages/util/dist/src/emulator.d.ts", "packages/util/dist/src/environment.d.ts", "packages/util/dist/src/compat.d.ts", - "packages/firestore/export.ts", + "packages/util/dist/src/obj.d.ts", "packages/firestore/src/protos/firestore_bundle_proto.ts", "packages/firestore/src/protos/firestore_proto_api.ts", "packages/firestore/src/util/error.ts", diff --git a/packages/firestore/index.bundle.ts b/packages/firestore/index.bundle.ts deleted file mode 100644 index 368c41bd8d4..00000000000 --- a/packages/firestore/index.bundle.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Firestore, loadBundle, namedQuery } from './export'; - -/** - * Prototype patches bundle loading to Firestore. - */ -export function registerBundle(instance: typeof Firestore): void { - instance.prototype.loadBundle = loadBundle; - instance.prototype.namedQuery = namedQuery; -} - -registerBundle(Firestore); diff --git a/packages/firestore/index.memory.ts b/packages/firestore/index.memory.ts deleted file mode 100644 index bc37d4281e4..00000000000 --- a/packages/firestore/index.memory.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '@firebase/app'; -import { FirebaseNamespace } from '@firebase/app-types'; - -import { Firestore, MemoryPersistenceProvider, ExpFirestore } from './export'; -import { name, version } from './package.json'; -import { configureForFirebase } from './src/config'; - -import './register-module'; - -/** - * Registers the memory-only Firestore build with the components framework. - */ -export function registerFirestore(instance: FirebaseNamespace): void { - configureForFirebase( - instance, - (app, auth) => - new Firestore( - app, - new ExpFirestore(app, auth), - new MemoryPersistenceProvider() - ) - ); - instance.registerVersion(name, version); -} - -registerFirestore(firebase); diff --git a/packages/firestore/index.node.memory.ts b/packages/firestore/index.node.memory.ts deleted file mode 100644 index 3f01a3607ca..00000000000 --- a/packages/firestore/index.node.memory.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '@firebase/app'; -import { FirebaseNamespace } from '@firebase/app-types'; - -import { name, version } from './package.json'; -import { Firestore, MemoryPersistenceProvider } from './src/api/database'; -import { configureForFirebase } from './src/config'; -import { Firestore as ExpFirestore } from './src/exp/database'; - -import './register-module'; - -/** - * Registers the memory-only Firestore build for Node with the components - * framework. - */ -export function registerFirestore(instance: FirebaseNamespace): void { - configureForFirebase( - instance, - (app, auth) => - new Firestore( - app, - new ExpFirestore(app, auth), - new MemoryPersistenceProvider() - ) - ); - instance.registerVersion(name, version, 'node'); -} - -registerFirestore(firebase); diff --git a/packages/firestore/index.node.ts b/packages/firestore/index.node.ts deleted file mode 100644 index f6eaeb8cba8..00000000000 --- a/packages/firestore/index.node.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import firebase from '@firebase/app'; -import { FirebaseNamespace } from '@firebase/app-types'; - -import { name, version } from './package.json'; -import { Firestore, IndexedDbPersistenceProvider } from './src/api/database'; -import { configureForFirebase } from './src/config'; -import { Firestore as ExpFirestore } from './src/exp/database'; - -import './register-module'; - -/** - * Registers the main Firestore Node build with the components framework. - * Persistence can be enabled via `firebase.firestore().enablePersistence()`. - */ -export function registerFirestore(instance: FirebaseNamespace): void { - configureForFirebase( - instance, - (app, auth) => - new Firestore( - app, - new ExpFirestore(app, auth), - new IndexedDbPersistenceProvider() - ) - ); - instance.registerVersion(name, version, 'node'); -} - -registerFirestore(firebase); diff --git a/packages/firestore/index.rn.memory.ts b/packages/firestore/index.rn.memory.ts deleted file mode 100644 index 6b0dd3d8c4b..00000000000 --- a/packages/firestore/index.rn.memory.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '@firebase/app'; -import { FirebaseNamespace } from '@firebase/app-types'; - -import { Firestore, MemoryPersistenceProvider, ExpFirestore } from './export'; -import { name, version } from './package.json'; -import { configureForFirebase } from './src/config'; - -import './register-module'; - -/** - * Registers the memory-only Firestore build for ReactNative with the components - * framework. - */ -export function registerFirestore(instance: FirebaseNamespace): void { - configureForFirebase( - instance, - (app, auth) => - new Firestore( - app, - new ExpFirestore(app, auth), - new MemoryPersistenceProvider() - ) - ); - instance.registerVersion(name, version, 'rn'); -} - -registerFirestore(firebase); diff --git a/packages/firestore/index.rn.ts b/packages/firestore/index.rn.ts deleted file mode 100644 index 3f843fe5be1..00000000000 --- a/packages/firestore/index.rn.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import firebase from '@firebase/app'; -import { FirebaseNamespace } from '@firebase/app-types'; - -import { - Firestore, - IndexedDbPersistenceProvider, - ExpFirestore -} from './export'; -import { name, version } from './package.json'; -import { configureForFirebase } from './src/config'; - -import './register-module'; - -/** - * Registers the main Firestore ReactNative build with the components framework. - * Persistence can be enabled via `firebase.firestore().enablePersistence()`. - */ -export function registerFirestore(instance: FirebaseNamespace): void { - configureForFirebase( - instance, - (app, auth) => - new Firestore( - app, - new ExpFirestore(app, auth), - new IndexedDbPersistenceProvider() - ) - ); - instance.registerVersion(name, version, 'rn'); -} - -registerFirestore(firebase); diff --git a/packages/firestore/index.ts b/packages/firestore/index.ts deleted file mode 100644 index f970abb1431..00000000000 --- a/packages/firestore/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '@firebase/app'; -import { FirebaseNamespace } from '@firebase/app-types'; - -import { - Firestore, - IndexedDbPersistenceProvider, - ExpFirestore -} from './export'; -import { name, version } from './package.json'; -import { configureForFirebase } from './src/config'; - -import './register-module'; - -/** - * Registers the main Firestore build with the components framework. - * Persistence can be enabled via `firebase.firestore().enablePersistence()`. - */ -export function registerFirestore(instance: FirebaseNamespace): void { - configureForFirebase( - instance, - (app, auth) => - new Firestore( - app, - new ExpFirestore(app, auth), - new IndexedDbPersistenceProvider() - ) - ); - instance.registerVersion(name, version); -} - -registerFirestore(firebase); diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index eb4ceef313a..81f7318ec2a 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -27,15 +27,16 @@ import { registerFirestore } from './register'; registerFirestore(); -export { FirestoreSettings as Settings } from '../src/lite/settings'; +export { FirestoreSettings as Settings } from '../src/lite-api/settings'; export { Firestore as Firestore, + EmulatorMockTokenOptions, initializeFirestore, getFirestore, terminate, connectFirestoreEmulator -} from '../src/lite/database'; +} from '../src/lite-api/database'; export { DocumentData, @@ -51,7 +52,7 @@ export { doc, refEqual, queryEqual -} from '../src/lite/reference'; +} from '../src/lite-api/reference'; export { endAt, @@ -67,7 +68,7 @@ export { query, QueryConstraint, QueryConstraintType -} from '../src/lite/query'; +} from '../src/lite-api/query'; export { addDoc, @@ -76,20 +77,20 @@ export { setDoc, getDoc, getDocs -} from '../src/lite/reference_impl'; +} from '../src/lite-api/reference_impl'; export { Primitive, NestedUpdateFields, AddPrefixToKeys, UnionToIntersection -} from '../src/lite/types'; +} from '../src/lite-api/types'; // TOOD(firestorelite): Add tests when Queries are usable -export { FieldPath, documentId } from '../src/lite/field_path'; +export { FieldPath, documentId } from '../src/lite-api/field_path'; // TOOD(firestorelite): Add tests when setDoc() is available -export { FieldValue } from '../src/lite/field_value'; +export { FieldValue } from '../src/lite-api/field_value'; export { increment, @@ -97,7 +98,7 @@ export { arrayUnion, serverTimestamp, deleteField -} from '../src/lite/field_value_impl'; +} from '../src/lite-api/field_value_impl'; export { FirestoreDataConverter, @@ -105,18 +106,18 @@ export { QueryDocumentSnapshot, QuerySnapshot, snapshotEqual -} from '../src/lite/snapshot'; +} from '../src/lite-api/snapshot'; -export { WriteBatch, writeBatch } from '../src/lite/write_batch'; +export { WriteBatch, writeBatch } from '../src/lite-api/write_batch'; -export { Transaction, runTransaction } from '../src/lite/transaction'; +export { Transaction, runTransaction } from '../src/lite-api/transaction'; export { setLogLevel, LogLevelString as LogLevel } from '../src/util/log'; -export { Bytes } from '../src/lite/bytes'; +export { Bytes } from '../src/lite-api/bytes'; -export { GeoPoint } from '../src/lite/geo_point'; +export { GeoPoint } from '../src/lite-api/geo_point'; -export { Timestamp } from '../src/lite/timestamp'; +export { Timestamp } from '../src/lite-api/timestamp'; export { FirestoreErrorCode, FirestoreError } from '../src/util/error'; diff --git a/packages/firestore/lite/register.ts b/packages/firestore/lite/register.ts index 24a73b56556..50a05aef5ed 100644 --- a/packages/firestore/lite/register.ts +++ b/packages/firestore/lite/register.ts @@ -19,13 +19,14 @@ import { _registerComponent, registerVersion, SDK_VERSION -} from '@firebase/app-exp'; +} from '@firebase/app'; import { Component, ComponentType } from '@firebase/component'; import { version } from '../package.json'; +import { LiteCredentialsProvider } from '../src/api/credentials'; import { setSDKVersion } from '../src/core/version'; -import { Firestore } from '../src/lite/database'; -import { FirestoreSettings } from '../src/lite/settings'; +import { Firestore } from '../src/lite-api/database'; +import { FirestoreSettings } from '../src/lite-api/settings'; declare module '@firebase/component' { interface NameServiceMapping { @@ -39,10 +40,10 @@ export function registerFirestore(): void { new Component( 'firestore/lite', (container, { options: settings }: { options?: FirestoreSettings }) => { - const app = container.getProvider('app-exp').getImmediate()!; + const app = container.getProvider('app').getImmediate()!; const firestoreInstance = new Firestore( app, - container.getProvider('auth-internal') + new LiteCredentialsProvider(container.getProvider('auth-internal')) ); if (settings) { firestoreInstance._setSettings(settings); diff --git a/packages/firestore/memory-bundle/package.json b/packages/firestore/memory-bundle/package.json deleted file mode 100644 index b29c357c34e..00000000000 --- a/packages/firestore/memory-bundle/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@firebase/firestore/memory-bundle", - "description": "Firestore bundle", - "main": "../dist/memory/node-cjs/bundle.js", - "main-esm2017": "../dist/memory/node-esm2017/bundle.js", - "react-native": "../dist/memory/rn/bundle.js", - "browser": "../dist/memory/esm5/bundle.js", - "module": "../dist/memory/esm5/bundle.js", - "esm2017": "../dist/memory/esm2017/bundle.js" -} diff --git a/packages/firestore/memory/package.json b/packages/firestore/memory/package.json deleted file mode 100644 index 0879ff8fa39..00000000000 --- a/packages/firestore/memory/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@firebase/firestore/memory", - "description": "A memory-only build of the Cloud Firestore JS SDK.", - "main": "../dist/memory/node-cjs/index.js", - "main-esm2017": "../dist/memory/node-esm2017/index.js", - "react-native": "../dist/memory/rn/index.js", - "browser": "../dist/memory/esm5/index.js", - "module": "../dist/memory/esm5/index.js", - "esm2017": "../dist/memory/esm2017/index.js", - "typings": "../dist/index.d.ts" -} diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 8f371295599..e19044d7b29 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -9,65 +9,55 @@ "scripts": { "bundle": "rollup -c", "prebuild": "tsc --emitDeclarationOnly --declaration -p tsconfig.json; yarn api-report", - "build": "run-p 'bundle rollup.config.browser.js' 'bundle rollup.config.node.js' 'bundle rollup.config.rn.js' build:lite build:exp && yarn build:compat", + "build": "run-p build:lite build:main", + "build:release": "yarn build && yarn typings:public", "build:scripts": "tsc -moduleResolution node --module commonjs scripts/*.ts && ls scripts/*.js | xargs -I % sh -c 'terser % -o %'", - "build:release": "run-p 'bundle rollup.config.browser.js' 'bundle rollup.config.node.js' 'bundle rollup.config.rn.js'", "build:deps": "lerna run --scope @firebase/firestore --include-dependencies build", - "build:compat": "run-p 'bundle ./rollup.config.browser.compat.js' 'bundle ./rollup.config.node.compat.js' 'bundle ./rollup.config.rn.compat.js' && yarn add-compat-overloads", - "build:console": "node tools/console.build.js", - "build:exp": "rollup -c rollup.config.exp.js", + "build:main": "rollup -c rollup.config.js", "build:lite": "rollup -c rollup.config.lite.js", - "build:exp:release": "yarn build:exp && yarn build:lite && yarn build:compat", - "postbuild:exp:release": "node ../../scripts/exp/remove-exp.js dist/exp/index.d.ts && node ../../scripts/exp/remove-exp.js dist/lite/index.d.ts", - "build:browser": "rollup -c rollup.config.browser.js", "predev": "yarn prebuild", "dev": "rollup -c -w", "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "prettier": "prettier --write '*.js' '*.ts' '@(lite|exp|src|test)/**/*.ts'", - "pregendeps:exp": "yarn api-report && node scripts/build-bundle.js --input ./exp/index.ts --output ./dist/exp/tmp.js", - "gendeps:exp": "../../scripts/exp/extract-deps.sh --types ./dist/exp/index.d.ts --bundle ./dist/exp/tmp.js --output ./dist/exp/dependencies.json", - "pregendeps:lite": "yarn api-report && node scripts/build-bundle.js --input ./lite/index.ts --output ./dist/lite/tmp.js", - "gendeps:lite": "../../scripts/exp/extract-deps.sh --types ./dist/lite/index.d.ts --bundle ./dist/lite/tmp.js --output ./dist/lite/dependencies.json", + "prettier": "prettier --write '*.js' '*.ts' '@(lite|src|test)/**/*.ts'", "test:lite": "node ./scripts/run-tests.js --emulator --platform node_lite --main=lite/index.ts 'test/lite/**/*.test.ts'", "test:lite:prod": "node ./scripts/run-tests.js --platform node_lite --main=lite/index.ts 'test/lite/**/*.test.ts'", "test:lite:browser": "karma start --single-run --lite", "test:lite:browser:debug": "karma start --browsers=Chrome --lite --auto-watch", + "pretest": "yarn test:prepare", + "pretest:ci": "yarn pretest", "test": "run-s lint test:all", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", "test:all": "run-p test:browser test:lite:browser test:travis test:minified", "test:browser": "karma start --single-run", "test:browser:debug": "karma start --browsers=Chrome --auto-watch", - "test:node": "node ./scripts/run-tests.js --main=index.node.ts --emulator 'test/{,!(browser|lite)/**/}*.test.ts'", - "test:node:prod": "node ./scripts/run-tests.js --main=index.node.ts 'test/{,!(browser|lite)/**/}*.test.ts'", - "test:node:persistence": "node ./scripts/run-tests.js --main=index.node.ts --persistence --emulator 'test/{,!(browser|lite)/**/}*.test.ts'", - "test:node:persistence:prod": "node ./scripts/run-tests.js --main=index.node.ts --persistence 'test/{,!(browser|lite)/**/}*.test.ts'", + "test:node": "node ./scripts/run-tests.js --main=test/register.ts --emulator 'test/{,!(browser|lite)/**/}*.test.ts'", + "test:node:prod": "ts-node-script ./scripts/run-tests.ts --main=test/register.ts 'test/{,!(browser|lite)/**/}*.test.ts'", + "test:node:persistence": "node ./scripts/run-tests.js --main=test/register.ts --persistence --emulator 'test/{,!(browser|lite)/**/}*.test.ts'", + "test:node:persistence:prod": "node ./scripts/run-tests.js --main=test/register.ts --persistence 'test/{,!(browser|lite)/**/}*.test.ts'", "test:travis": "ts-node --compiler-options='{\"module\":\"commonjs\"}' ../../scripts/emulator-testing/firestore-test-runner.ts", "test:minified": "(cd ../../integration/firestore ; yarn test)", - "api-report:exp": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore --packageRoot . --typescriptDts ./dist/firestore/exp/index.d.ts --rollupDts ./dist/exp/private.d.ts --untrimmedRollupDts ./dist/exp/internal.d.ts --publicDts ./dist/exp/index.d.ts", + "api-report:main": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore --packageRoot . --typescriptDts ./dist/firestore/src/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/index.d.ts", "api-report:lite": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore-lite --packageRoot . --typescriptDts ./dist/firestore/lite/index.d.ts --rollupDts ./dist/lite/private.d.ts --untrimmedRollupDts ./dist/lite/internal.d.ts --publicDts ./dist/lite/index.d.ts", "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", - "api-report": "run-s api-report:exp api-report:lite && yarn api-report:api-json", - "predoc": "node ../../scripts/exp/remove-exp.js temp", + "api-report": "run-s api-report:main api-report:lite && yarn api-report:api-json", "doc": "api-documenter markdown --input temp --output docs", - "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i dist/exp/index.d.ts -o dist/compat/esm2017/firestore/compat/index.d.ts -a -r FirebaseFirestore:types.FirebaseFirestore -r CollectionReference:types.CollectionReference -r DocumentReference:types.DocumentReference -r Query:types.Query -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/firestore" + "test:prepare": "node ./scripts/prepare-test.js", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/index.d.ts" }, - "main": "dist/node-cjs/index.js", - "main-esm2017": "dist/node-esm2017/index.js", - "react-native": "dist/rn/index.js", - "browser": "dist/esm5/index.js", - "module": "dist/esm5/index.js", - "esm2017": "dist/esm2017/index.js", + "main": "dist/index.node.cjs.js", + "main-esm": "dist/index.node.cjs.esm2017.js", + "react-native": "dist/index.rn.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "esm5": "dist/index.esm5.js", "license": "Apache-2.0", "files": [ "dist", - "memory/package.json", - "bundle/package.json", - "memory-bundle/package.json" + "lite/package.json" ], "dependencies": { "@firebase/component": "0.5.6", - "@firebase/firestore-types": "2.4.0", "@firebase/logger": "0.2.6", "@firebase/util": "1.3.0", "@firebase/webchannel-wrapper": "0.5.1", @@ -77,14 +67,13 @@ "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" }, "devDependencies": { "@firebase/app": "0.6.30", + "@firebase/app-compat": "0.0.900", "@rollup/plugin-alias": "3.1.2", "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "11.2.0", "@types/eslint": "7.2.10", "@types/json-stable-stringify": "1.0.32", "json-stable-stringify": "1.0.1", @@ -107,7 +96,7 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/firestore/index.d.ts", + "typings": "dist/firestore/src/index.d.ts", "nyc": { "extension": [ ".ts" diff --git a/packages/firestore/rollup.config.browser.compat.js b/packages/firestore/rollup.config.browser.compat.js deleted file mode 100644 index 94384ddf8a6..00000000000 --- a/packages/firestore/rollup.config.browser.compat.js +++ /dev/null @@ -1,69 +0,0 @@ -import pkg from './compat/package.json'; -import path from 'path'; -import { getImportPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const util = require('./rollup.shared'); - -export default [ - // Create main build - { - input: { - index: './compat/index.ts' - }, - output: { - dir: 'dist/compat/esm2017', - format: 'es', - sourcemap: true - }, - plugins: [ - ...util.es2017PluginsCompat( - 'browser', - getImportPathTransformer({ - // ../../exp/index - pattern: /^.*exp\/index$/, - template: ['@firebase/firestore'] - }), - /* mangled= */ false - ) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Convert main build to ES5 - { - input: { - index: path.resolve('./compat', pkg['browser']) - }, - output: [ - { - dir: 'dist/compat/esm5', - format: 'es', - sourcemap: true - } - ], - plugins: util.es2017ToEs5Plugins(/* mangled= */ true), - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - } -]; diff --git a/packages/firestore/rollup.config.browser.js b/packages/firestore/rollup.config.browser.js deleted file mode 100644 index fbfe1af418f..00000000000 --- a/packages/firestore/rollup.config.browser.js +++ /dev/null @@ -1,120 +0,0 @@ -import pkg from './package.json'; -import path from 'path'; -import memoryPkg from './memory/package.json'; -import bundlePkg from './bundle/package.json'; -import memoryBundlePkg from './memory-bundle/package.json'; - -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const util = require('./rollup.shared'); - -export default [ - // Create a temporary build that includes the mangled classes for all exports - { - input: 'export.ts', - output: { - file: 'dist/prebuilt.js', - format: 'es', - sourcemap: true - }, - plugins: util.es2017Plugins('browser', /* mangled= */ true), - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - }, - onwarn: util.onwarn - }, - // Create main build - { - input: { - index: 'index.ts', - bundle: 'index.bundle.ts' - }, - output: { - dir: 'dist/esm2017', - format: 'es', - sourcemap: true - }, - plugins: [ - util.applyPrebuilt(), - ...util.es2017Plugins('browser', /* mangled= */ false) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Convert main build to ES5 - { - input: { - index: pkg['esm2017'], - bundle: path.resolve('./bundle', bundlePkg['esm2017']) - }, - output: [ - { - dir: 'dist/esm5', - format: 'es', - sourcemap: true - } - ], - plugins: util.es2017ToEs5Plugins(/* mangled= */ true), - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Create memory build - { - input: { - index: 'index.memory.ts', - bundle: 'index.bundle.ts' - }, - output: { - dir: 'dist/memory/esm2017', - format: 'es', - sourcemap: true - }, - plugins: [ - util.applyPrebuilt(), - ...util.es2017Plugins('browser', /* mangled= */ false) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Convert memory build to ES5 - { - input: { - index: path.resolve('./memory', memoryPkg['esm2017']), - bundle: path.resolve('./bundle', memoryBundlePkg['esm2017']) - }, - output: [ - { - dir: 'dist/memory/esm5', - format: 'es', - sourcemap: true - } - ], - plugins: util.es2017ToEs5Plugins(/* mangled= */ true), - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - } -]; diff --git a/packages/firestore/rollup.config.exp.js b/packages/firestore/rollup.config.js similarity index 79% rename from packages/firestore/rollup.config.exp.js rename to packages/firestore/rollup.config.js index 1367380c3e7..d34960c310f 100644 --- a/packages/firestore/rollup.config.exp.js +++ b/packages/firestore/rollup.config.js @@ -20,13 +20,11 @@ import json from '@rollup/plugin-json'; import alias from '@rollup/plugin-alias'; import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; -import path from 'path'; import replace from 'rollup-plugin-replace'; import copy from 'rollup-plugin-copy'; import { terser } from 'rollup-plugin-terser'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import pkg from './exp/package.json'; +import pkg from './package.json'; const util = require('./rollup.shared'); @@ -41,7 +39,7 @@ const nodePlugins = function () { }, cacheDir: tmp.dirSync(), abortOnError: false, - transformers: [util.removeAssertTransformer, importPathTransformer] + transformers: [util.removeAssertTransformer] }), json({ preferConst: true }), // Needed as we also use the *.proto files @@ -49,7 +47,7 @@ const nodePlugins = function () { targets: [ { src: 'src/protos', - dest: 'dist/exp/src' + dest: 'dist/src' } ] }), @@ -71,10 +69,7 @@ const browserPlugins = function () { cacheDir: tmp.dirSync(), clean: true, abortOnError: false, - transformers: [ - util.removeAssertAndPrefixInternalTransformer, - importPathTransformer - ] + transformers: [util.removeAssertAndPrefixInternalTransformer] }), json({ preferConst: true }), terser(util.manglePrivatePropertiesOptions) @@ -84,9 +79,9 @@ const browserPlugins = function () { const allBuilds = [ // Node ESM build { - input: './exp/index.node.ts', + input: './src/index.node.ts', output: { - file: path.resolve('./exp', pkg['main-esm']), + file: pkg['main-esm'], format: 'es', sourcemap: true }, @@ -99,9 +94,9 @@ const allBuilds = [ }, // Node CJS build { - input: path.resolve('./exp', pkg['main-esm']), + input: pkg['main-esm'], output: { - file: path.resolve('./exp', pkg.main), + file: pkg.main, format: 'cjs', sourcemap: true }, @@ -113,9 +108,9 @@ const allBuilds = [ }, // Browser build { - input: './exp/index.ts', + input: './src/index.ts', output: { - file: path.resolve('./exp', pkg.browser), + file: pkg.browser, format: 'es', sourcemap: true }, @@ -127,10 +122,10 @@ const allBuilds = [ }, // Convert es2017 build to ES5 { - input: path.resolve('./exp', pkg['browser']), + input: pkg['browser'], output: [ { - file: path.resolve('./exp', pkg['esm5']), + file: pkg['esm5'], format: 'es', sourcemap: true } @@ -143,9 +138,9 @@ const allBuilds = [ }, // RN build { - input: './exp/index.rn.ts', + input: './src/index.rn.ts', output: { - file: path.resolve('./exp', pkg['react-native']), + file: pkg['react-native'], format: 'es', sourcemap: true }, diff --git a/packages/firestore/rollup.config.node.compat.js b/packages/firestore/rollup.config.node.compat.js deleted file mode 100644 index b7d8d0ff791..00000000000 --- a/packages/firestore/rollup.config.node.compat.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import replace from 'rollup-plugin-replace'; -import copy from 'rollup-plugin-copy'; -import pkg from './compat/package.json'; -import path from 'path'; -import { getImportPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -const util = require('./rollup.shared'); - -export default [ - { - input: { - index: './compat/index.node.ts' - }, - output: { - dir: 'dist/compat/node-esm2017', - format: 'es', - sourcemap: true - }, - plugins: [ - ...util.es2017PluginsCompat( - 'node', - getImportPathTransformer({ - // ../../exp/index - pattern: /^.*exp\/index$/, - template: ['@firebase/firestore'] - }) - ), - replace({ - 'process.env.FIRESTORE_PROTO_ROOT': JSON.stringify('../protos') - }), - copy({ - targets: [{ src: 'src/protos', dest: 'dist' }] - }) - ], - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - }, - onwarn: util.onwarn - }, - { - input: { - index: path.resolve('./compat', pkg['main-esm2017']) - }, - output: [ - { - dir: 'dist/compat/node-cjs', - format: 'cjs', - sourcemap: true - } - ], - plugins: util.es2017ToEs5Plugins(), - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - } - } -]; diff --git a/packages/firestore/rollup.config.node.js b/packages/firestore/rollup.config.node.js deleted file mode 100644 index 6554fb1a629..00000000000 --- a/packages/firestore/rollup.config.node.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import replace from 'rollup-plugin-replace'; -import copy from 'rollup-plugin-copy'; -import pkg from './package.json'; -import bundlePkg from './bundle/package.json'; -import memoryPkg from './memory/package.json'; -import memoryBundlePkg from './memory-bundle/package.json'; -import path from 'path'; - -const util = require('./rollup.shared'); - -export default [ - { - input: { - index: 'index.node.ts', - bundle: 'index.bundle.ts' - }, - output: { - dir: 'dist/node-esm2017', - format: 'es', - sourcemap: true - }, - plugins: [ - ...util.es2017Plugins('node'), - replace({ - 'process.env.FIRESTORE_PROTO_ROOT': JSON.stringify('../protos') - }), - copy({ - targets: [{ src: 'src/protos', dest: 'dist' }] - }) - ], - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - }, - onwarn: util.onwarn - }, - { - input: { - index: pkg['main-esm2017'], - bundle: path.resolve('./bundle', bundlePkg['main-esm2017']) - }, - output: [ - { - dir: 'dist/node-cjs', - format: 'cjs', - sourcemap: true - } - ], - plugins: util.es2017ToEs5Plugins(), - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - } - }, - { - input: { - index: 'index.node.memory.ts', - bundle: 'index.bundle.ts' - }, - output: { - dir: 'dist/memory/node-esm2017', - format: 'es', - sourcemap: true - }, - plugins: [ - ...util.es2017Plugins('node'), - replace({ - 'process.env.FIRESTORE_PROTO_ROOT': JSON.stringify('../protos') - }), - copy({ - targets: [{ src: 'src/protos', dest: 'dist/memory' }] - }) - ], - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - } - }, - { - input: { - index: path.resolve('./memory', memoryPkg['main-esm2017']), - bundle: path.resolve('./bundle', memoryBundlePkg['main-esm2017']) - }, - output: [ - { - dir: 'dist/memory/node-cjs', - format: 'cjs', - sourcemap: true - } - ], - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - } - } -]; diff --git a/packages/firestore/rollup.config.rn.compat.js b/packages/firestore/rollup.config.rn.compat.js deleted file mode 100644 index 3aab7b7fb37..00000000000 --- a/packages/firestore/rollup.config.rn.compat.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const util = require('./rollup.shared'); -import { getImportPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -export default [ - // Create main build - { - input: { - index: './compat/index.ts' - }, - output: { - dir: 'dist/compat/rn', - format: 'es', - sourcemap: true - }, - plugins: [ - ...util.es2017PluginsCompat( - 'rn', - getImportPathTransformer({ - // ../../exp/index - pattern: /^.*exp\/index$/, - template: ['@firebase/firestore'] - }), - /* mangled= */ false - ) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - } -]; diff --git a/packages/firestore/rollup.config.rn.js b/packages/firestore/rollup.config.rn.js deleted file mode 100644 index b89adc481bf..00000000000 --- a/packages/firestore/rollup.config.rn.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const util = require('./rollup.shared'); - -export default [ - // Create a temporary build that includes the mangled classes for all exports - { - input: 'export.ts', - output: { - file: 'dist/prebuilt.rn.js', - format: 'es', - sourcemap: true - }, - plugins: util.es2017Plugins('rn', /* mangled= */ true), - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - }, - onwarn: util.onwarn - }, - // Create main build - { - input: { - index: 'index.ts', - bundle: 'index.bundle.ts' - }, - output: { - dir: 'dist/rn', - format: 'es', - sourcemap: true - }, - plugins: [ - util.applyPrebuilt('prebuilt.rn.js'), - ...util.es2017Plugins('rn', /* mangled= */ false) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Create memory build - { - input: { - index: 'index.memory.ts', - bundle: 'index.bundle.ts' - }, - output: { - dir: 'dist/memory/rn', - format: 'es', - sourcemap: true - }, - plugins: [ - util.applyPrebuilt('prebuilt.rn.js'), - ...util.es2017Plugins('rn', /* mangled= */ false) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - } -]; diff --git a/packages/firestore/scripts/prepare-test.js b/packages/firestore/scripts/prepare-test.js new file mode 100644 index 00000000000..208647deed1 --- /dev/null +++ b/packages/firestore/scripts/prepare-test.js @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +"use strict";var __spreadArray=this&&this.__spreadArray||function(to,from){for(var i=0,il=from.length,j=to.length;i Promise; * listening for changes. */ export interface CredentialsProvider { + /** + * Starts the credentials provider and specifies a listener to be notified of + * credential changes (sign-in / sign-out, token changes). It is immediately + * called once with the initial user. + * + * The change listener is invoked on the provided AsyncQueue. + */ + start(asyncQueue: AsyncQueue, changeListener: CredentialChangeListener): void; + /** Requests a token for the current user. */ getToken(): Promise; @@ -96,53 +105,26 @@ export interface CredentialsProvider { */ invalidateToken(): void; - /** - * Specifies a listener to be notified of credential changes - * (sign-in / sign-out, token changes). It is immediately called once with the - * initial user. - * - * The change listener is invoked on the provided AsyncQueue. - */ - setChangeListener( - asyncQueue: AsyncQueue, - changeListener: CredentialChangeListener - ): void; - - /** Removes the previously-set change listener. */ - removeChangeListener(): void; + shutdown(): void; } /** A CredentialsProvider that always yields an empty token. */ export class EmptyCredentialsProvider implements CredentialsProvider { - /** - * Stores the listener registered with setChangeListener() - * This isn't actually necessary since the UID never changes, but we use this - * to verify the listen contract is adhered to in tests. - */ - private changeListener: CredentialChangeListener | null = null; - getToken(): Promise { return Promise.resolve(null); } invalidateToken(): void {} - setChangeListener( + start( asyncQueue: AsyncQueue, changeListener: CredentialChangeListener ): void { - debugAssert( - !this.changeListener, - 'Can only call setChangeListener() once.' - ); - this.changeListener = changeListener; // Fire with initial user. asyncQueue.enqueueRetryable(() => changeListener(User.UNAUTHENTICATED)); } - removeChangeListener(): void { - this.changeListener = null; - } + shutdown(): void {} } /** @@ -165,7 +147,7 @@ export class EmulatorCredentialsProvider implements CredentialsProvider { invalidateToken(): void {} - setChangeListener( + start( asyncQueue: AsyncQueue, changeListener: CredentialChangeListener ): void { @@ -178,80 +160,148 @@ export class EmulatorCredentialsProvider implements CredentialsProvider { asyncQueue.enqueueRetryable(() => changeListener(this.token.user)); } - removeChangeListener(): void { + shutdown(): void { this.changeListener = null; } } +/** Credential provider for the Lite SDK. */ +export class LiteCredentialsProvider implements CredentialsProvider { + private auth: FirebaseAuthInternal | null = null; + + constructor(authProvider: Provider) { + authProvider.onInit(auth => { + this.auth = auth; + }); + } + + getToken(): Promise { + if (!this.auth) { + return Promise.resolve(null); + } + + return this.auth.getToken().then(tokenData => { + if (tokenData) { + hardAssert( + typeof tokenData.accessToken === 'string', + 'Invalid tokenData returned from getToken():' + tokenData + ); + return new OAuthToken( + tokenData.accessToken, + new User(this.auth!.getUid()) + ); + } else { + return null; + } + }); + } + + invalidateToken(): void {} + + start( + asyncQueue: AsyncQueue, + changeListener: CredentialChangeListener + ): void {} + + shutdown(): void {} +} + export class FirebaseCredentialsProvider implements CredentialsProvider { /** * The auth token listener registered with FirebaseApp, retained here so we * can unregister it. */ - private tokenListener: () => void; + private tokenListener!: () => void; /** Tracks the current User. */ private currentUser: User = User.UNAUTHENTICATED; - /** Promise that allows blocking on the initialization of Firebase Auth. */ - private authDeferred = new Deferred(); - /** * Counter used to detect if the token changed while a getToken request was * outstanding. */ private tokenCounter = 0; - /** The listener registered with setChangeListener(). */ - private changeListener?: CredentialChangeListener; - private forceRefresh = false; private auth: FirebaseAuthInternal | null = null; - private asyncQueue: AsyncQueue | null = null; + constructor(private authProvider: Provider) {} + + start( + asyncQueue: AsyncQueue, + changeListener: CredentialChangeListener + ): void { + let lastTokenId = this.tokenCounter; + + // A change listener that prevents double-firing for the same token change. + const guardedChangeListener: (user: User) => Promise = user => { + if (this.tokenCounter !== lastTokenId) { + lastTokenId = this.tokenCounter; + return changeListener(user); + } else { + return Promise.resolve(); + } + }; + + // A promise that can be waited on to block on the next token change. + // This promise is re-created after each change. + let nextToken = new Deferred(); - constructor(authProvider: Provider) { this.tokenListener = () => { this.tokenCounter++; this.currentUser = this.getUser(); - this.authDeferred.resolve(); - if (this.changeListener) { - this.asyncQueue!.enqueueRetryable(() => - this.changeListener!(this.currentUser) - ); - } + nextToken.resolve(); + nextToken = new Deferred(); + asyncQueue.enqueueRetryable(() => + guardedChangeListener(this.currentUser) + ); }; const registerAuth = (auth: FirebaseAuthInternal): void => { - logDebug('FirebaseCredentialsProvider', 'Auth detected'); - this.auth = auth; - this.auth.addAuthTokenListener(this.tokenListener); + asyncQueue.enqueueRetryable(async () => { + logDebug('FirebaseCredentialsProvider', 'Auth detected'); + this.auth = auth; + this.auth.addAuthTokenListener(this.tokenListener); + + // Call the change listener inline to block on the user change. + await nextToken.promise; + await guardedChangeListener(this.currentUser); + }); }; - authProvider.onInit(auth => registerAuth(auth)); + this.authProvider.onInit(auth => registerAuth(auth)); // Our users can initialize Auth right after Firestore, so we give it // a chance to register itself with the component framework before we // determine whether to start up in unauthenticated mode. setTimeout(() => { if (!this.auth) { - const auth = authProvider.getImmediate({ optional: true }); + const auth = this.authProvider.getImmediate({ optional: true }); if (auth) { registerAuth(auth); } else { // If auth is still not available, proceed with `null` user logDebug('FirebaseCredentialsProvider', 'Auth not yet detected'); - this.authDeferred.resolve(); + nextToken.resolve(); + nextToken = new Deferred(); } } }, 0); + + asyncQueue.enqueueRetryable(async () => { + // If we have not received a token, wait for the first one. + if (this.tokenCounter === 0) { + await nextToken.promise; + await guardedChangeListener(this.currentUser); + } + }); } getToken(): Promise { debugAssert( this.tokenListener != null, - 'getToken cannot be called after listener removed.' + 'FirebaseCredentialsProvider not started.' ); // Take note of the current value of the tokenCounter so that this method @@ -293,26 +343,10 @@ export class FirebaseCredentialsProvider implements CredentialsProvider { this.forceRefresh = true; } - setChangeListener( - asyncQueue: AsyncQueue, - changeListener: CredentialChangeListener - ): void { - debugAssert(!this.asyncQueue, 'Can only call setChangeListener() once.'); - this.asyncQueue = asyncQueue; - - // Blocks the AsyncQueue until the next user is available. - this.asyncQueue!.enqueueRetryable(async () => { - await this.authDeferred.promise; - await changeListener(this.currentUser); - this.changeListener = changeListener; - }); - } - - removeChangeListener(): void { + shutdown(): void { if (this.auth) { this.auth.removeAuthTokenListener(this.tokenListener!); } - this.changeListener = () => Promise.resolve(); } // Auth.getUid() can return null even with a user logged in. It is because @@ -389,7 +423,7 @@ export class FirstPartyCredentialsProvider implements CredentialsProvider { ); } - setChangeListener( + start( asyncQueue: AsyncQueue, changeListener: CredentialChangeListener ): void { @@ -397,7 +431,7 @@ export class FirstPartyCredentialsProvider implements CredentialsProvider { asyncQueue.enqueueRetryable(() => changeListener(User.FIRST_PARTY)); } - removeChangeListener(): void {} + shutdown(): void {} invalidateToken(): void {} } diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index d8e4847233e..c4a74e4e541 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,1316 +15,540 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types'; -import { _FirebaseApp, FirebaseService } from '@firebase/app-types/private'; +// eslint-disable-next-line import/no-extraneous-dependencies import { - CollectionReference as PublicCollectionReference, - DocumentChange as PublicDocumentChange, - DocumentChangeType as PublicDocumentChangeType, - DocumentData, - DocumentData as PublicDocumentData, - DocumentReference as PublicDocumentReference, - DocumentSnapshot as PublicDocumentSnapshot, - FieldPath as PublicFieldPath, - FirebaseFirestore as PublicFirestore, - FirestoreDataConverter as PublicFirestoreDataConverter, - GetOptions as PublicGetOptions, - LogLevel as PublicLogLevel, - OrderByDirection as PublicOrderByDirection, - PersistenceSettings as PublicPersistenceSettings, - Query as PublicQuery, - QueryDocumentSnapshot as PublicQueryDocumentSnapshot, - QuerySnapshot as PublicQuerySnapshot, - SetOptions as PublicSetOptions, - Settings as PublicSettings, - SnapshotListenOptions as PublicSnapshotListenOptions, - SnapshotOptions as PublicSnapshotOptions, - Transaction as PublicTransaction, - UpdateData as PublicUpdateData, - WhereFilterOp as PublicWhereFilterOp, - WriteBatch as PublicWriteBatch -} from '@firebase/firestore-types'; -import { - Compat, - EmulatorMockTokenOptions, - getModularInstance -} from '@firebase/util'; + _getProvider, + _removeServiceInstance, + FirebaseApp, + getApp +} from '@firebase/app'; +import { deepEqual } from '@firebase/util'; import { - LoadBundleTask, - Bytes, - clearIndexedDbPersistence, - disableNetwork, - enableIndexedDbPersistence, - enableMultiTabIndexedDbPersistence, - enableNetwork, - ensureFirestoreConfigured, - Firestore as ExpFirestore, - connectFirestoreEmulator, - waitForPendingWrites, - FieldPath as ExpFieldPath, - limit, - limitToLast, - where, - orderBy, - startAfter, - startAt, - query, - endBefore, - endAt, - doc, - collection, - collectionGroup, - queryEqual, - Query as ExpQuery, - CollectionReference as ExpCollectionReference, - DocumentReference as ExpDocumentReference, - refEqual, - addDoc, - deleteDoc, - executeWrite, - getDoc, - getDocFromCache, - getDocFromServer, - getDocs, - getDocsFromCache, - getDocsFromServer, - onSnapshot, - onSnapshotsInSync, - setDoc, - updateDoc, - Unsubscribe, - DocumentChange as ExpDocumentChange, - DocumentSnapshot as ExpDocumentSnapshot, - QueryDocumentSnapshot as ExpQueryDocumentSnapshot, - QuerySnapshot as ExpQuerySnapshot, - snapshotEqual, - SnapshotMetadata, - runTransaction, - Transaction as ExpTransaction, - WriteBatch as ExpWriteBatch, - AbstractUserDataWriter -} from '../../exp/index'; // import from the exp public API + IndexedDbOfflineComponentProvider, + MultiTabOfflineComponentProvider, + OfflineComponentProvider, + OnlineComponentProvider +} from '../core/component_provider'; import { DatabaseId } from '../core/database_info'; -import { PartialWithFieldValue, WithFieldValue } from '../lite/reference'; -import { UntypedFirestoreDataConverter } from '../lite/user_data_reader'; -import { DocumentKey } from '../model/document_key'; -import { FieldPath, ResourcePath } from '../model/path'; +import { + FirestoreClient, + firestoreClientDisableNetwork, + firestoreClientEnableNetwork, + firestoreClientGetNamedQuery, + firestoreClientLoadBundle, + firestoreClientWaitForPendingWrites, + setOfflineComponentProvider, + setOnlineComponentProvider +} from '../core/firestore_client'; +import { makeDatabaseInfo } from '../lite-api/components'; +import { Firestore as LiteFirestore } from '../lite-api/database'; +import { Query } from '../lite-api/reference'; +import { + indexedDbClearPersistence, + indexedDbStoragePrefix +} from '../local/indexeddb_persistence'; +import { LRU_COLLECTION_DISABLED } from '../local/lru_garbage_collector'; +import { LRU_MINIMUM_CACHE_SIZE_BYTES } from '../local/lru_garbage_collector_impl'; import { debugAssert } from '../util/assert'; -import { ByteString } from '../util/byte_string'; +import { AsyncQueue } from '../util/async_queue'; +import { newAsyncQueue } from '../util/async_queue_impl'; import { Code, FirestoreError } from '../util/error'; -import { - cast, - validateIsNotUsedTogether, - validateSetOptions -} from '../util/input_validation'; -import { logWarn, setLogLevel as setClientLogLevel } from '../util/log'; +import { cast } from '../util/input_validation'; +import { Deferred } from '../util/promise'; -import { Blob } from './blob'; -import { - CompleteFn, - ErrorFn, - isPartialObserver, - NextFn, - PartialObserver -} from './observer'; +import { LoadBundleTask } from './bundle'; +import { CredentialsProvider } from './credentials'; +import { PersistenceSettings, FirestoreSettings } from './settings'; +export { + connectFirestoreEmulator, + EmulatorMockTokenOptions +} from '../lite-api/database'; -/** - * A persistence provider for either memory-only or IndexedDB persistence. - * Mainly used to allow optional inclusion of IndexedDB code. - */ -export interface PersistenceProvider { - enableIndexedDbPersistence( - firestore: Firestore, - forceOwnership: boolean - ): Promise; - enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise; - clearIndexedDbPersistence(firestore: Firestore): Promise; +declare module '@firebase/component' { + interface NameServiceMapping { + 'firestore': Firestore; + } } -const MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE = - 'You are using the memory-only build of Firestore. Persistence support is ' + - 'only available via the @firebase/firestore bundle or the ' + - 'firebase-firestore.js build.'; +/** DOMException error code constants. */ +const DOM_EXCEPTION_INVALID_STATE = 11; +const DOM_EXCEPTION_ABORTED = 20; +const DOM_EXCEPTION_QUOTA_EXCEEDED = 22; /** - * The persistence provider included with the memory-only SDK. This provider - * errors for all attempts to access persistence. + * Constant used to indicate the LRU garbage collection should be disabled. + * Set this value as the `cacheSizeBytes` on the settings passed to the + * {@link Firestore} instance. */ -export class MemoryPersistenceProvider implements PersistenceProvider { - enableIndexedDbPersistence( - firestore: Firestore, - forceOwnership: boolean - ): Promise { - throw new FirestoreError( - Code.FAILED_PRECONDITION, - MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE - ); - } - - enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise { - throw new FirestoreError( - Code.FAILED_PRECONDITION, - MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE - ); - } - - clearIndexedDbPersistence(firestore: Firestore): Promise { - throw new FirestoreError( - Code.FAILED_PRECONDITION, - MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE - ); - } -} +export const CACHE_SIZE_UNLIMITED = LRU_COLLECTION_DISABLED; /** - * The persistence provider included with the full Firestore SDK. + * The Cloud Firestore service interface. + * + * Do not call this constructor directly. Instead, use {@link getFirestore}. */ -export class IndexedDbPersistenceProvider implements PersistenceProvider { - enableIndexedDbPersistence( - firestore: Firestore, - forceOwnership: boolean - ): Promise { - return enableIndexedDbPersistence(firestore._delegate, { forceOwnership }); - } - enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise { - return enableMultiTabIndexedDbPersistence(firestore._delegate); - } - clearIndexedDbPersistence(firestore: Firestore): Promise { - return clearIndexedDbPersistence(firestore._delegate); - } -} +export class Firestore extends LiteFirestore { + /** + * Whether it's a {@link Firestore} or Firestore Lite instance. + */ + type: 'firestore-lite' | 'firestore' = 'firestore'; -/** - * Compat class for Firestore. Exposes Firestore Legacy API, but delegates - * to the functional API of firestore-exp. - */ -export class Firestore - implements PublicFirestore, FirebaseService, Compat -{ - _appCompat?: FirebaseApp; + readonly _queue: AsyncQueue = newAsyncQueue(); + readonly _persistenceKey: string; + + _firestoreClient: FirestoreClient | undefined; + + /** @hideconstructor */ constructor( databaseIdOrApp: DatabaseId | FirebaseApp, - readonly _delegate: ExpFirestore, - private _persistenceProvider: PersistenceProvider + credentialsProvider: CredentialsProvider ) { - if (!(databaseIdOrApp instanceof DatabaseId)) { - this._appCompat = databaseIdOrApp as FirebaseApp; - } - } - - get _databaseId(): DatabaseId { - return this._delegate._databaseId; - } - - settings(settingsLiteral: PublicSettings): void { - const currentSettings = this._delegate._getSettings(); - if ( - !settingsLiteral.merge && - currentSettings.host !== settingsLiteral.host - ) { - logWarn( - 'You are overriding the original host. If you did not intend ' + - 'to override your settings, use {merge: true}.' - ); - } - - if (settingsLiteral.merge) { - settingsLiteral = { - ...currentSettings, - ...settingsLiteral - }; - // Remove the property from the settings once the merge is completed - delete settingsLiteral.merge; - } - - this._delegate._setSettings(settingsLiteral); - } - - useEmulator( - host: string, - port: number, - options: { - mockUserToken?: EmulatorMockTokenOptions | string; - } = {} - ): void { - connectFirestoreEmulator(this._delegate, host, port, options); - } - - enableNetwork(): Promise { - return enableNetwork(this._delegate); + super(databaseIdOrApp, credentialsProvider); + this._persistenceKey = + 'name' in databaseIdOrApp ? databaseIdOrApp.name : '[DEFAULT]'; } - disableNetwork(): Promise { - return disableNetwork(this._delegate); - } - - enablePersistence(settings?: PublicPersistenceSettings): Promise { - let synchronizeTabs = false; - let experimentalForceOwningTab = false; - - if (settings) { - synchronizeTabs = !!settings.synchronizeTabs; - experimentalForceOwningTab = !!settings.experimentalForceOwningTab; - - validateIsNotUsedTogether( - 'synchronizeTabs', - synchronizeTabs, - 'experimentalForceOwningTab', - experimentalForceOwningTab - ); - } - - return synchronizeTabs - ? this._persistenceProvider.enableMultiTabIndexedDbPersistence(this) - : this._persistenceProvider.enableIndexedDbPersistence( - this, - experimentalForceOwningTab - ); - } - - clearPersistence(): Promise { - return this._persistenceProvider.clearIndexedDbPersistence(this); - } - - terminate(): Promise { - if (this._appCompat) { - (this._appCompat as _FirebaseApp)._removeServiceInstance('firestore'); - (this._appCompat as _FirebaseApp)._removeServiceInstance('firestore-exp'); + _terminate(): Promise { + if (!this._firestoreClient) { + // The client must be initialized to ensure that all subsequent API + // usage throws an exception. + configureFirestore(this); } - return this._delegate._delete(); - } - - waitForPendingWrites(): Promise { - return waitForPendingWrites(this._delegate); - } - - onSnapshotsInSync(observer: PartialObserver): Unsubscribe; - onSnapshotsInSync(onSync: () => void): Unsubscribe; - onSnapshotsInSync(arg: unknown): Unsubscribe { - return onSnapshotsInSync(this._delegate, arg as PartialObserver); + return this._firestoreClient!.terminate(); } +} - get app(): FirebaseApp { - if (!this._appCompat) { +/** + * Initializes a new instance of {@link Firestore} with the provided settings. + * Can only be called before any other function, including + * {@link getFirestore}. If the custom settings are empty, this function is + * equivalent to calling {@link getFirestore}. + * + * @param app - The {@link @firebase/app#FirebaseApp} with which the {@link Firestore} instance will + * be associated. + * @param settings - A settings object to configure the {@link Firestore} instance. + * @returns A newly initialized {@link Firestore} instance. + */ +export function initializeFirestore( + app: FirebaseApp, + settings: FirestoreSettings +): Firestore { + const provider = _getProvider(app, 'firestore'); + + if (provider.isInitialized()) { + const existingInstance = provider.getImmediate(); + const initialSettings = provider.getOptions() as FirestoreSettings; + if (deepEqual(initialSettings, settings)) { + return existingInstance; + } else { throw new FirestoreError( Code.FAILED_PRECONDITION, - "Firestore was not initialized using the Firebase SDK. 'app' is " + - 'not available' - ); - } - return this._appCompat as FirebaseApp; - } - - INTERNAL = { - delete: () => this.terminate() - }; - - collection(pathString: string): PublicCollectionReference { - try { - return new CollectionReference( - this, - collection(this._delegate, pathString) - ); - } catch (e) { - throw replaceFunctionName(e, 'collection()', 'Firestore.collection()'); - } - } - - doc(pathString: string): PublicDocumentReference { - try { - return new DocumentReference(this, doc(this._delegate, pathString)); - } catch (e) { - throw replaceFunctionName(e, 'doc()', 'Firestore.doc()'); - } - } - - collectionGroup(collectionId: string): PublicQuery { - try { - return new Query(this, collectionGroup(this._delegate, collectionId)); - } catch (e) { - throw replaceFunctionName( - e, - 'collectionGroup()', - 'Firestore.collectionGroup()' + 'initializeFirestore() has already been called with ' + + 'different options. To avoid this error, call initializeFirestore() with the ' + + 'same options as when it was originally called, or call getFirestore() to return the' + + ' already initialized instance.' ); } } - runTransaction( - updateFunction: (transaction: PublicTransaction) => Promise - ): Promise { - return runTransaction(this._delegate, transaction => - updateFunction(new Transaction(this, transaction)) - ); - } - - batch(): PublicWriteBatch { - ensureFirestoreConfigured(this._delegate); - return new WriteBatch( - new ExpWriteBatch(this._delegate, mutations => - executeWrite(this._delegate, mutations) - ) - ); - } - - loadBundle( - bundleData: ArrayBuffer | ReadableStream | string - ): LoadBundleTask { + if ( + settings.cacheSizeBytes !== undefined && + settings.cacheSizeBytes !== CACHE_SIZE_UNLIMITED && + settings.cacheSizeBytes < LRU_MINIMUM_CACHE_SIZE_BYTES + ) { throw new FirestoreError( - Code.FAILED_PRECONDITION, - '"loadBundle()" does not exist, have you imported "firebase/firestore/bundle"?' + Code.INVALID_ARGUMENT, + `cacheSizeBytes must be at least ${LRU_MINIMUM_CACHE_SIZE_BYTES}` ); } - namedQuery(name: string): Promise | null> { - throw new FirestoreError( - Code.FAILED_PRECONDITION, - '"namedQuery()" does not exist, have you imported "firebase/firestore/bundle"?' - ); - } + return provider.initialize({ options: settings }); } -export class UserDataWriter extends AbstractUserDataWriter { - constructor(protected firestore: Firestore) { - super(); - } - - protected convertBytes(bytes: ByteString): Blob { - return new Blob(new Bytes(bytes)); - } +/** + * Returns the existing {@link Firestore} instance that is associated with the + * provided {@link @firebase/app#FirebaseApp}. If no instance exists, initializes a new + * instance with default settings. + * + * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned {@link Firestore} + * instance is associated with. + * @returns The {@link Firestore} instance of the provided app. + */ +export function getFirestore(app: FirebaseApp = getApp()): Firestore { + return _getProvider(app, 'firestore').getImmediate() as Firestore; +} - protected convertReference(name: string): DocumentReference { - const key = this.convertDocumentKey(name, this.firestore._databaseId); - return DocumentReference.forKey(key, this.firestore, /* converter= */ null); - } +/** + * @internal + */ +export function ensureFirestoreConfigured( + firestore: Firestore +): FirestoreClient { + if (!firestore._firestoreClient) { + configureFirestore(firestore); + } + firestore._firestoreClient!.verifyNotTerminated(); + return firestore._firestoreClient as FirestoreClient; } -export function setLogLevel(level: PublicLogLevel): void { - setClientLogLevel(level); +export function configureFirestore(firestore: Firestore): void { + const settings = firestore._freezeSettings(); + debugAssert(!!settings.host, 'FirestoreSettings.host is not set'); + debugAssert( + !firestore._firestoreClient, + 'configureFirestore() called multiple times' + ); + + const databaseInfo = makeDatabaseInfo( + firestore._databaseId, + firestore._app?.options.appId || '', + firestore._persistenceKey, + settings + ); + firestore._firestoreClient = new FirestoreClient( + firestore._credentials, + firestore._queue, + databaseInfo + ); } /** - * A reference to a transaction. + * Attempts to enable persistent storage, if possible. + * + * Must be called before any other functions (other than + * {@link initializeFirestore}, {@link getFirestore} or + * {@link clearIndexedDbPersistence}. + * + * If this fails, `enableIndexedDbPersistence()` will reject the promise it + * returns. Note that even after this failure, the {@link Firestore} instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @param persistenceSettings - Optional settings object to configure + * persistence. + * @returns A `Promise` that represents successfully enabling persistent storage. */ -export class Transaction implements PublicTransaction, Compat { - private _userDataWriter: UserDataWriter; - - constructor( - private readonly _firestore: Firestore, - readonly _delegate: ExpTransaction - ) { - this._userDataWriter = new UserDataWriter(_firestore); - } - - get( - documentRef: PublicDocumentReference - ): Promise> { - const ref = castReference(documentRef); - return this._delegate - .get(ref) - .then( - result => - new DocumentSnapshot( - this._firestore, - new ExpDocumentSnapshot( - this._firestore._delegate, - this._userDataWriter, - result._key, - result._document, - result.metadata, - ref.converter - ) - ) - ); - } - - set( - documentRef: DocumentReference, - data: Partial, - options: PublicSetOptions - ): Transaction; - set(documentRef: DocumentReference, data: T): Transaction; - set( - documentRef: PublicDocumentReference, - data: T | Partial, - options?: PublicSetOptions - ): Transaction { - const ref = castReference(documentRef); - if (options) { - validateSetOptions('Transaction.set', options); - this._delegate.set(ref, data as PartialWithFieldValue, options); - } else { - this._delegate.set(ref, data as WithFieldValue); - } - return this; - } - - update( - documentRef: PublicDocumentReference, - data: PublicUpdateData - ): Transaction; - update( - documentRef: PublicDocumentReference, - field: string | PublicFieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] - ): Transaction; - update( - documentRef: PublicDocumentReference, - dataOrField: unknown, - value?: unknown, - ...moreFieldsAndValues: unknown[] - ): Transaction { - const ref = castReference(documentRef); - if (arguments.length === 2) { - this._delegate.update(ref, dataOrField as PublicUpdateData); - } else { - this._delegate.update( - ref, - dataOrField as string | ExpFieldPath, - value, - ...moreFieldsAndValues - ); - } - - return this; - } - - delete(documentRef: PublicDocumentReference): Transaction { - const ref = castReference(documentRef); - this._delegate.delete(ref); - return this; - } +export function enableIndexedDbPersistence( + firestore: Firestore, + persistenceSettings?: PersistenceSettings +): Promise { + firestore = cast(firestore, Firestore); + verifyNotInitialized(firestore); + + const client = ensureFirestoreConfigured(firestore); + const settings = firestore._freezeSettings(); + + const onlineComponentProvider = new OnlineComponentProvider(); + const offlineComponentProvider = new IndexedDbOfflineComponentProvider( + onlineComponentProvider, + settings.cacheSizeBytes, + persistenceSettings?.forceOwnership + ); + return setPersistenceProviders( + client, + onlineComponentProvider, + offlineComponentProvider + ); } -export class WriteBatch implements PublicWriteBatch, Compat { - constructor(readonly _delegate: ExpWriteBatch) {} - set( - documentRef: DocumentReference, - data: Partial, - options: PublicSetOptions - ): WriteBatch; - set(documentRef: DocumentReference, data: T): WriteBatch; - set( - documentRef: PublicDocumentReference, - data: T | Partial, - options?: PublicSetOptions - ): WriteBatch { - const ref = castReference(documentRef); - if (options) { - validateSetOptions('WriteBatch.set', options); - this._delegate.set(ref, data as PartialWithFieldValue, options); - } else { - this._delegate.set(ref, data as WithFieldValue); - } - return this; - } - - update( - documentRef: PublicDocumentReference, - data: PublicUpdateData - ): WriteBatch; - update( - documentRef: PublicDocumentReference, - field: string | PublicFieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] - ): WriteBatch; - update( - documentRef: PublicDocumentReference, - dataOrField: string | PublicFieldPath | PublicUpdateData, - value?: unknown, - ...moreFieldsAndValues: unknown[] - ): WriteBatch { - const ref = castReference(documentRef); - if (arguments.length === 2) { - this._delegate.update(ref, dataOrField as PublicUpdateData); - } else { - this._delegate.update( - ref, - dataOrField as string | ExpFieldPath, - value, - ...moreFieldsAndValues - ); - } - return this; - } - - delete(documentRef: PublicDocumentReference): WriteBatch { - const ref = castReference(documentRef); - this._delegate.delete(ref); - return this; - } - - commit(): Promise { - return this._delegate.commit(); - } +/** + * Attempts to enable multi-tab persistent storage, if possible. If enabled + * across all tabs, all operations share access to local persistence, including + * shared execution of queries and latency-compensated local document updates + * across all connected instances. + * + * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise + * it returns. Note that even after this failure, the {@link Firestore} instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab and + * multi-tab is not enabled. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The {@link Firestore} instance to enable persistence for. + * @returns A `Promise` that represents successfully enabling persistent + * storage. + */ +export function enableMultiTabIndexedDbPersistence( + firestore: Firestore +): Promise { + firestore = cast(firestore, Firestore); + verifyNotInitialized(firestore); + + const client = ensureFirestoreConfigured(firestore); + const settings = firestore._freezeSettings(); + + const onlineComponentProvider = new OnlineComponentProvider(); + const offlineComponentProvider = new MultiTabOfflineComponentProvider( + onlineComponentProvider, + settings.cacheSizeBytes + ); + return setPersistenceProviders( + client, + onlineComponentProvider, + offlineComponentProvider + ); } /** - * Wraps a `PublicFirestoreDataConverter` translating the types from the - * experimental SDK into corresponding types from the Classic SDK before passing - * them to the wrapped converter. + * Registers both the `OfflineComponentProvider` and `OnlineComponentProvider`. + * If the operation fails with a recoverable error (see + * `canRecoverFromIndexedDbError()` below), the returned Promise is rejected + * but the client remains usable. */ -class FirestoreDataConverter - implements - UntypedFirestoreDataConverter, - Compat> -{ - private static readonly INSTANCES = new WeakMap(); - - private constructor( - private readonly _firestore: Firestore, - private readonly _userDataWriter: UserDataWriter, - readonly _delegate: PublicFirestoreDataConverter - ) {} +function setPersistenceProviders( + client: FirestoreClient, + onlineComponentProvider: OnlineComponentProvider, + offlineComponentProvider: OfflineComponentProvider +): Promise { + const persistenceResult = new Deferred(); + return client.asyncQueue + .enqueue(async () => { + try { + await setOfflineComponentProvider(client, offlineComponentProvider); + await setOnlineComponentProvider(client, onlineComponentProvider); + persistenceResult.resolve(); + } catch (e) { + if (!canFallbackFromIndexedDbError(e)) { + throw e; + } + console.warn( + 'Error enabling offline persistence. Falling back to ' + + 'persistence disabled: ' + + e + ); + persistenceResult.reject(e); + } + }) + .then(() => persistenceResult.promise); +} - fromFirestore( - snapshot: ExpQueryDocumentSnapshot, - options?: PublicSnapshotOptions - ): U { - const expSnapshot = new ExpQueryDocumentSnapshot( - this._firestore._delegate, - this._userDataWriter, - snapshot._key, - snapshot._document, - snapshot.metadata, - /* converter= */ null +/** + * Decides whether the provided error allows us to gracefully disable + * persistence (as opposed to crashing the client). + */ +function canFallbackFromIndexedDbError( + error: FirestoreError | DOMException +): boolean { + if (error.name === 'FirebaseError') { + return ( + error.code === Code.FAILED_PRECONDITION || + error.code === Code.UNIMPLEMENTED ); - return this._delegate.fromFirestore( - new QueryDocumentSnapshot(this._firestore, expSnapshot), - options ?? {} + } else if ( + typeof DOMException !== 'undefined' && + error instanceof DOMException + ) { + // There are a few known circumstances where we can open IndexedDb but + // trying to read/write will fail (e.g. quota exceeded). For + // well-understood cases, we attempt to detect these and then gracefully + // fall back to memory persistence. + // NOTE: Rather than continue to add to this list, we could decide to + // always fall back, with the risk that we might accidentally hide errors + // representing actual SDK bugs. + return ( + // When the browser is out of quota we could get either quota exceeded + // or an aborted error depending on whether the error happened during + // schema migration. + error.code === DOM_EXCEPTION_QUOTA_EXCEEDED || + error.code === DOM_EXCEPTION_ABORTED || + // Firefox Private Browsing mode disables IndexedDb and returns + // INVALID_STATE for any usage. + error.code === DOM_EXCEPTION_INVALID_STATE ); } - toFirestore(modelObject: WithFieldValue): PublicDocumentData; - toFirestore( - modelObject: PartialWithFieldValue, - options: PublicSetOptions - ): PublicDocumentData; - toFirestore( - modelObject: WithFieldValue | PartialWithFieldValue, - options?: PublicSetOptions - ): PublicDocumentData { - if (!options) { - return this._delegate.toFirestore(modelObject as U); - } else { - return this._delegate.toFirestore(modelObject as Partial, options); - } - } - - // Use the same instance of `FirestoreDataConverter` for the given instances - // of `Firestore` and `PublicFirestoreDataConverter` so that isEqual() will - // compare equal for two objects created with the same converter instance. - static getInstance( - firestore: Firestore, - converter: PublicFirestoreDataConverter - ): FirestoreDataConverter { - const converterMapByFirestore = FirestoreDataConverter.INSTANCES; - let untypedConverterByConverter = converterMapByFirestore.get(firestore); - if (!untypedConverterByConverter) { - untypedConverterByConverter = new WeakMap(); - converterMapByFirestore.set(firestore, untypedConverterByConverter); - } - - let instance = untypedConverterByConverter.get(converter); - if (!instance) { - instance = new FirestoreDataConverter( - firestore, - new UserDataWriter(firestore), - converter - ); - untypedConverterByConverter.set(converter, instance); - } - - return instance; - } + return true; } /** - * A reference to a particular document in a collection in the database. + * Clears the persistent storage. This includes pending writes and cached + * documents. + * + * Must be called while the {@link Firestore} instance is not started (after the app is + * terminated or when the app is first initialized). On startup, this function + * must be called before other functions (other than {@link + * initializeFirestore} or {@link getFirestore})). If the {@link Firestore} + * instance is still running, the promise will be rejected with the error code + * of `failed-precondition`. + * + * Note: `clearIndexedDbPersistence()` is primarily intended to help write + * reliable tests that use Cloud Firestore. It uses an efficient mechanism for + * dropping existing data but does not attempt to securely overwrite or + * otherwise make cached data unrecoverable. For applications that are sensitive + * to the disclosure of cached data in between user sessions, we strongly + * recommend not enabling persistence at all. + * + * @param firestore - The {@link Firestore} instance to clear persistence for. + * @returns A `Promise` that is resolved when the persistent storage is + * cleared. Otherwise, the promise is rejected with an error. */ -export class DocumentReference - implements PublicDocumentReference, Compat> -{ - private _userDataWriter: UserDataWriter; - - constructor( - readonly firestore: Firestore, - readonly _delegate: ExpDocumentReference - ) { - this._userDataWriter = new UserDataWriter(firestore); - } - - static forPath( - path: ResourcePath, - firestore: Firestore, - converter: UntypedFirestoreDataConverter | null - ): DocumentReference { - if (path.length % 2 !== 0) { - throw new FirestoreError( - Code.INVALID_ARGUMENT, - 'Invalid document reference. Document ' + - 'references must have an even number of segments, but ' + - `${path.canonicalString()} has ${path.length}` - ); - } - return new DocumentReference( - firestore, - new ExpDocumentReference( - firestore._delegate, - converter, - new DocumentKey(path) - ) - ); - } - - static forKey( - key: DocumentKey, - firestore: Firestore, - converter: UntypedFirestoreDataConverter | null - ): DocumentReference { - return new DocumentReference( - firestore, - new ExpDocumentReference(firestore._delegate, converter, key) +export function clearIndexedDbPersistence(firestore: Firestore): Promise { + if (firestore._initialized && !firestore._terminated) { + throw new FirestoreError( + Code.FAILED_PRECONDITION, + 'Persistence can only be cleared before a Firestore instance is ' + + 'initialized or after it is terminated.' ); } - get id(): string { - return this._delegate.id; - } - - get parent(): PublicCollectionReference { - return new CollectionReference(this.firestore, this._delegate.parent); - } - - get path(): string { - return this._delegate.path; - } - - collection( - pathString: string - ): PublicCollectionReference { + const deferred = new Deferred(); + firestore._queue.enqueueAndForgetEvenWhileRestricted(async () => { try { - return new CollectionReference( - this.firestore, - collection(this._delegate, pathString) + await indexedDbClearPersistence( + indexedDbStoragePrefix(firestore._databaseId, firestore._persistenceKey) ); + deferred.resolve(); } catch (e) { - throw replaceFunctionName( - e, - 'collection()', - 'DocumentReference.collection()' - ); + deferred.reject(e); } - } - - isEqual(other: PublicDocumentReference): boolean { - other = getModularInstance>(other); - - if (!(other instanceof ExpDocumentReference)) { - return false; - } - return refEqual(this._delegate, other); - } - - set(value: Partial, options: PublicSetOptions): Promise; - set(value: T): Promise; - set(value: T | Partial, options?: PublicSetOptions): Promise { - options = validateSetOptions('DocumentReference.set', options); - try { - if (options) { - return setDoc( - this._delegate, - value as PartialWithFieldValue, - options - ); - } else { - return setDoc(this._delegate, value as WithFieldValue); - } - } catch (e) { - throw replaceFunctionName(e, 'setDoc()', 'DocumentReference.set()'); - } - } - - update(value: PublicUpdateData): Promise; - update( - field: string | PublicFieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] - ): Promise; - update( - fieldOrUpdateData: string | PublicFieldPath | PublicUpdateData, - value?: unknown, - ...moreFieldsAndValues: unknown[] - ): Promise { - try { - if (arguments.length === 1) { - return updateDoc(this._delegate, fieldOrUpdateData as PublicUpdateData); - } else { - return updateDoc( - this._delegate, - fieldOrUpdateData as string | ExpFieldPath, - value, - ...moreFieldsAndValues - ); - } - } catch (e) { - throw replaceFunctionName(e, 'updateDoc()', 'DocumentReference.update()'); - } - } - - delete(): Promise { - return deleteDoc(this._delegate); - } - - onSnapshot(observer: PartialObserver>): Unsubscribe; - onSnapshot( - options: PublicSnapshotListenOptions, - observer: PartialObserver> - ): Unsubscribe; - onSnapshot( - onNext: NextFn>, - onError?: ErrorFn, - onCompletion?: CompleteFn - ): Unsubscribe; - onSnapshot( - options: PublicSnapshotListenOptions, - onNext: NextFn>, - onError?: ErrorFn, - onCompletion?: CompleteFn - ): Unsubscribe; - - onSnapshot(...args: unknown[]): Unsubscribe { - const options = extractSnapshotOptions(args); - const observer = wrapObserver, ExpDocumentSnapshot>( - args, - result => - new DocumentSnapshot( - this.firestore, - new ExpDocumentSnapshot( - this.firestore._delegate, - this._userDataWriter, - result._key, - result._document, - result.metadata, - this._delegate.converter - ) - ) - ); - return onSnapshot(this._delegate, options, observer); - } - - get(options?: PublicGetOptions): Promise> { - let snap: Promise>; - if (options?.source === 'cache') { - snap = getDocFromCache(this._delegate); - } else if (options?.source === 'server') { - snap = getDocFromServer(this._delegate); - } else { - snap = getDoc(this._delegate); - } - - return snap.then( - result => - new DocumentSnapshot( - this.firestore, - new ExpDocumentSnapshot( - this.firestore._delegate, - this._userDataWriter, - result._key, - result._document, - result.metadata, - this._delegate.converter as UntypedFirestoreDataConverter - ) - ) - ); - } - - withConverter(converter: null): PublicDocumentReference; - withConverter( - converter: PublicFirestoreDataConverter - ): PublicDocumentReference; - withConverter( - converter: PublicFirestoreDataConverter | null - ): PublicDocumentReference { - return new DocumentReference( - this.firestore, - converter - ? this._delegate.withConverter( - FirestoreDataConverter.getInstance(this.firestore, converter) - ) - : (this._delegate.withConverter(null) as ExpDocumentReference) - ); - } + }); + return deferred.promise; } /** - * Replaces the function name in an error thrown by the firestore-exp API - * with the function names used in the classic API. + * Waits until all currently pending writes for the active user have been + * acknowledged by the backend. + * + * The returned promise resolves immediately if there are no outstanding writes. + * Otherwise, the promise waits for all previously issued writes (including + * those written in a previous app session), but it does not wait for writes + * that were added after the function is called. If you want to wait for + * additional writes, call `waitForPendingWrites()` again. + * + * Any outstanding `waitForPendingWrites()` promises are rejected during user + * changes. + * + * @returns A `Promise` which resolves when all currently pending writes have been + * acknowledged by the backend. */ -function replaceFunctionName( - e: Error, - original: string | RegExp, - updated: string -): Error { - e.message = e.message.replace(original, updated); - return e; +export function waitForPendingWrites(firestore: Firestore): Promise { + firestore = cast(firestore, Firestore); + const client = ensureFirestoreConfigured(firestore); + return firestoreClientWaitForPendingWrites(client); } /** - * Iterates the list of arguments from an `onSnapshot` call and returns the - * first argument that may be an `SnapshotListenOptions` object. Returns an - * empty object if none is found. + * Re-enables use of the network for this {@link Firestore} instance after a prior + * call to {@link disableNetwork}. + * + * @returns A `Promise` that is resolved once the network has been enabled. */ -export function extractSnapshotOptions( - args: unknown[] -): PublicSnapshotListenOptions { - for (const arg of args) { - if (typeof arg === 'object' && !isPartialObserver(arg)) { - return arg as PublicSnapshotListenOptions; - } - } - return {}; +export function enableNetwork(firestore: Firestore): Promise { + firestore = cast(firestore, Firestore); + const client = ensureFirestoreConfigured(firestore); + return firestoreClientEnableNetwork(client); } /** - * Creates an observer that can be passed to the firestore-exp SDK. The - * observer converts all observed values into the format expected by the classic - * SDK. + * Disables network usage for this instance. It can be re-enabled via {@link + * enableNetwork}. While the network is disabled, any snapshot listeners, + * `getDoc()` or `getDocs()` calls will return results from cache, and any write + * operations will be queued until the network is restored. * - * @param args - The list of arguments from an `onSnapshot` call. - * @param wrapper - The function that converts the firestore-exp type into the - * type used by this shim. + * @returns A `Promise` that is resolved once the network has been disabled. */ -export function wrapObserver( - args: unknown[], - wrapper: (val: ExpType) => CompatType -): PartialObserver { - let userObserver: PartialObserver; - if (isPartialObserver(args[0])) { - userObserver = args[0] as PartialObserver; - } else if (isPartialObserver(args[1])) { - userObserver = args[1]; - } else if (typeof args[0] === 'function') { - userObserver = { - next: args[0] as NextFn | undefined, - error: args[1] as ErrorFn | undefined, - complete: args[2] as CompleteFn | undefined - }; - } else { - userObserver = { - next: args[1] as NextFn | undefined, - error: args[2] as ErrorFn | undefined, - complete: args[3] as CompleteFn | undefined - }; - } - - return { - next: val => { - if (userObserver!.next) { - userObserver!.next(wrapper(val)); - } - }, - error: userObserver.error?.bind(userObserver), - complete: userObserver.complete?.bind(userObserver) - }; +export function disableNetwork(firestore: Firestore): Promise { + firestore = cast(firestore, Firestore); + const client = ensureFirestoreConfigured(firestore); + return firestoreClientDisableNetwork(client); } /** - * Options interface that can be provided to configure the deserialization of - * DocumentSnapshots. + * Terminates the provided {@link Firestore} instance. + * + * After calling `terminate()` only the `clearIndexedDbPersistence()` function + * may be used. Any other function will throw a `FirestoreError`. + * + * To restart after termination, create a new instance of FirebaseFirestore with + * {@link getFirestore}. + * + * Termination does not cancel any pending writes, and any promises that are + * awaiting a response from the server will not be resolved. If you have + * persistence enabled, the next time you start this instance, it will resume + * sending these writes to the server. + * + * Note: Under normal circumstances, calling `terminate()` is not required. This + * function is useful only when you want to force this instance to release all + * of its resources or in combination with `clearIndexedDbPersistence()` to + * ensure that all local state is destroyed between test runs. + * + * @returns A `Promise` that is resolved when the instance has been successfully + * terminated. */ -export interface SnapshotOptions extends PublicSnapshotOptions {} - -export class DocumentSnapshot - implements PublicDocumentSnapshot, Compat> -{ - constructor( - private readonly _firestore: Firestore, - readonly _delegate: ExpDocumentSnapshot - ) {} - - get ref(): DocumentReference { - return new DocumentReference(this._firestore, this._delegate.ref); - } - - get id(): string { - return this._delegate.id; - } - - get metadata(): SnapshotMetadata { - return this._delegate.metadata; - } - - get exists(): boolean { - return this._delegate.exists(); - } - - data(options?: PublicSnapshotOptions): T | undefined { - return this._delegate.data(options); - } - - get( - fieldPath: string | PublicFieldPath, - options?: PublicSnapshotOptions - // We are using `any` here to avoid an explicit cast by our users. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ): any { - return this._delegate.get(fieldPath as string | ExpFieldPath, options); - } - - isEqual(other: DocumentSnapshot): boolean { - return snapshotEqual(this._delegate, other._delegate); - } -} - -export class QueryDocumentSnapshot - extends DocumentSnapshot - implements PublicQueryDocumentSnapshot -{ - data(options?: PublicSnapshotOptions): T { - const data = this._delegate.data(options); - debugAssert( - data !== undefined, - 'Document in a QueryDocumentSnapshot should exist' - ); - return data; - } +export function terminate(firestore: Firestore): Promise { + _removeServiceInstance(firestore.app, 'firestore'); + return firestore._delete(); } -export class Query - implements PublicQuery, Compat> -{ - private readonly _userDataWriter: UserDataWriter; - - constructor(readonly firestore: Firestore, readonly _delegate: ExpQuery) { - this._userDataWriter = new UserDataWriter(firestore); - } - - where( - fieldPath: string | FieldPath, - opStr: PublicWhereFilterOp, - value: unknown - ): Query { - try { - // The "as string" cast is a little bit of a hack. `where` accepts the - // FieldPath Compat type as input, but is not typed as such in order to - // not expose this via our public typings file. - return new Query( - this.firestore, - query(this._delegate, where(fieldPath as string, opStr, value)) - ); - } catch (e) { - throw replaceFunctionName(e, /(orderBy|where)\(\)/, 'Query.$1()'); - } - } - - orderBy( - fieldPath: string | FieldPath, - directionStr?: PublicOrderByDirection - ): Query { - try { - // The "as string" cast is a little bit of a hack. `orderBy` accepts the - // FieldPath Compat type as input, but is not typed as such in order to - // not expose this via our public typings file. - return new Query( - this.firestore, - query(this._delegate, orderBy(fieldPath as string, directionStr)) - ); - } catch (e) { - throw replaceFunctionName(e, /(orderBy|where)\(\)/, 'Query.$1()'); - } - } - - limit(n: number): Query { - try { - return new Query(this.firestore, query(this._delegate, limit(n))); - } catch (e) { - throw replaceFunctionName(e, 'limit()', 'Query.limit()'); - } - } - - limitToLast(n: number): Query { - try { - return new Query( - this.firestore, - query(this._delegate, limitToLast(n)) - ); - } catch (e) { - throw replaceFunctionName(e, 'limitToLast()', 'Query.limitToLast()'); - } - } - - startAt(...args: any[]): Query { - try { - return new Query(this.firestore, query(this._delegate, startAt(...args))); - } catch (e) { - throw replaceFunctionName(e, 'startAt()', 'Query.startAt()'); - } - } - - startAfter(...args: any[]): Query { - try { - return new Query( - this.firestore, - query(this._delegate, startAfter(...args)) - ); - } catch (e) { - throw replaceFunctionName(e, 'startAfter()', 'Query.startAfter()'); - } - } - - endBefore(...args: any[]): Query { - try { - return new Query( - this.firestore, - query(this._delegate, endBefore(...args)) - ); - } catch (e) { - throw replaceFunctionName(e, 'endBefore()', 'Query.endBefore()'); - } - } - - endAt(...args: any[]): Query { - try { - return new Query(this.firestore, query(this._delegate, endAt(...args))); - } catch (e) { - throw replaceFunctionName(e, 'endAt()', 'Query.endAt()'); - } - } - - isEqual(other: PublicQuery): boolean { - return queryEqual(this._delegate, (other as Query)._delegate); - } - - get(options?: PublicGetOptions): Promise> { - let query: Promise>; - if (options?.source === 'cache') { - query = getDocsFromCache(this._delegate); - } else if (options?.source === 'server') { - query = getDocsFromServer(this._delegate); - } else { - query = getDocs(this._delegate); - } - return query.then( - result => - new QuerySnapshot( - this.firestore, - new ExpQuerySnapshot( - this.firestore._delegate, - this._userDataWriter, - this._delegate, - result._snapshot - ) - ) - ); - } - - onSnapshot(observer: PartialObserver>): Unsubscribe; - onSnapshot( - options: PublicSnapshotListenOptions, - observer: PartialObserver> - ): Unsubscribe; - onSnapshot( - onNext: NextFn>, - onError?: ErrorFn, - onCompletion?: CompleteFn - ): Unsubscribe; - onSnapshot( - options: PublicSnapshotListenOptions, - onNext: NextFn>, - onError?: ErrorFn, - onCompletion?: CompleteFn - ): Unsubscribe; - - onSnapshot(...args: unknown[]): Unsubscribe { - const options = extractSnapshotOptions(args); - const observer = wrapObserver, ExpQuerySnapshot>( - args, - snap => - new QuerySnapshot( - this.firestore, - new ExpQuerySnapshot( - this.firestore._delegate, - this._userDataWriter, - this._delegate, - snap._snapshot - ) - ) - ); - return onSnapshot(this._delegate, options, observer); - } - - withConverter(converter: null): Query; - withConverter(converter: PublicFirestoreDataConverter): Query; - withConverter( - converter: PublicFirestoreDataConverter | null - ): Query { - return new Query( - this.firestore, - converter - ? this._delegate.withConverter( - FirestoreDataConverter.getInstance(this.firestore, converter) - ) - : (this._delegate.withConverter(null) as ExpQuery) - ); - } -} - -export class DocumentChange - implements PublicDocumentChange, Compat> -{ - constructor( - private readonly _firestore: Firestore, - readonly _delegate: ExpDocumentChange - ) {} - - get type(): PublicDocumentChangeType { - return this._delegate.type; - } - - get doc(): QueryDocumentSnapshot { - return new QueryDocumentSnapshot(this._firestore, this._delegate.doc); - } - - get oldIndex(): number { - return this._delegate.oldIndex; - } - - get newIndex(): number { - return this._delegate.newIndex; - } +/** + * Loads a Firestore bundle into the local cache. + * + * @param firestore - The {@link Firestore} instance to load bundles for for. + * @param bundleData - An object representing the bundle to be loaded. Valid objects are + * `ArrayBuffer`, `ReadableStream` or `string`. + * + * @returns + * A `LoadBundleTask` object, which notifies callers with progress updates, and completion + * or error events. It can be used as a `Promise`. + */ +export function loadBundle( + firestore: Firestore, + bundleData: ReadableStream | ArrayBuffer | string +): LoadBundleTask { + firestore = cast(firestore, Firestore); + const client = ensureFirestoreConfigured(firestore); + const resultTask = new LoadBundleTask(); + firestoreClientLoadBundle( + client, + firestore._databaseId, + bundleData, + resultTask + ); + return resultTask; } -export class QuerySnapshot - implements PublicQuerySnapshot, Compat> -{ - constructor( - readonly _firestore: Firestore, - readonly _delegate: ExpQuerySnapshot - ) {} - - get query(): Query { - return new Query(this._firestore, this._delegate.query); - } - - get metadata(): SnapshotMetadata { - return this._delegate.metadata; - } - - get size(): number { - return this._delegate.size; - } - - get empty(): boolean { - return this._delegate.empty; - } - - get docs(): Array> { - return this._delegate.docs.map( - doc => new QueryDocumentSnapshot(this._firestore, doc) - ); - } - - docChanges( - options?: PublicSnapshotListenOptions - ): Array> { - return this._delegate - .docChanges(options) - .map(docChange => new DocumentChange(this._firestore, docChange)); - } - - forEach( - callback: (result: QueryDocumentSnapshot) => void, - thisArg?: unknown - ): void { - this._delegate.forEach(snapshot => { - callback.call( - thisArg, - new QueryDocumentSnapshot(this._firestore, snapshot) - ); - }); - } - - isEqual(other: QuerySnapshot): boolean { - return snapshotEqual(this._delegate, other._delegate); - } +/** + * Reads a Firestore {@link Query} from local cache, identified by the given name. + * + * The named queries are packaged into bundles on the server side (along + * with resulting documents), and loaded to local cache using `loadBundle`. Once in local + * cache, use this method to extract a {@link Query} by name. + */ +export function namedQuery( + firestore: Firestore, + name: string +): Promise { + firestore = cast(firestore, Firestore); + const client = ensureFirestoreConfigured(firestore); + return firestoreClientGetNamedQuery(client, name).then(namedQuery => { + if (!namedQuery) { + return null; + } + + return new Query(firestore, null, namedQuery.query); + }); } -export class CollectionReference - extends Query - implements PublicCollectionReference -{ - constructor( - readonly firestore: Firestore, - readonly _delegate: ExpCollectionReference - ) { - super(firestore, _delegate); - } - - get id(): string { - return this._delegate.id; - } - - get path(): string { - return this._delegate.path; - } - - get parent(): DocumentReference | null { - const docRef = this._delegate.parent; - return docRef ? new DocumentReference(this.firestore, docRef) : null; - } - - doc(documentPath?: string): DocumentReference { - try { - if (documentPath === undefined) { - // Call `doc` without `documentPath` if `documentPath` is `undefined` - // as `doc` validates the number of arguments to prevent users from - // accidentally passing `undefined`. - return new DocumentReference(this.firestore, doc(this._delegate)); - } else { - return new DocumentReference( - this.firestore, - doc(this._delegate, documentPath) - ); - } - } catch (e) { - throw replaceFunctionName(e, 'doc()', 'CollectionReference.doc()'); - } - } - - add(data: T): Promise> { - return addDoc(this._delegate, data as WithFieldValue).then( - docRef => new DocumentReference(this.firestore, docRef) - ); - } - - isEqual(other: CollectionReference): boolean { - return refEqual(this._delegate, other._delegate); - } - - withConverter(converter: null): CollectionReference; - withConverter( - converter: PublicFirestoreDataConverter - ): CollectionReference; - withConverter( - converter: PublicFirestoreDataConverter | null - ): CollectionReference { - return new CollectionReference( - this.firestore, - converter - ? this._delegate.withConverter( - FirestoreDataConverter.getInstance(this.firestore, converter) - ) - : (this._delegate.withConverter(null) as ExpCollectionReference) +function verifyNotInitialized(firestore: Firestore): void { + if (firestore._initialized || firestore._terminated) { + throw new FirestoreError( + Code.FAILED_PRECONDITION, + 'Firestore has already been started and persistence can no longer be ' + + 'enabled. You can only enable persistence before calling any other ' + + 'methods on a Firestore object.' ); } } - -function castReference( - documentRef: PublicDocumentReference -): ExpDocumentReference { - return cast>(documentRef, ExpDocumentReference); -} diff --git a/packages/firestore/src/api/field_path.ts b/packages/firestore/src/api/field_path.ts index 2b1ef4442fc..7c14d757aae 100644 --- a/packages/firestore/src/api/field_path.ts +++ b/packages/firestore/src/api/field_path.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,49 +15,4 @@ * limitations under the License. */ -import { FieldPath as PublicFieldPath } from '@firebase/firestore-types'; -import { Compat, getModularInstance } from '@firebase/util'; - -import { FieldPath as ExpFieldPath } from '../../exp/index'; -import { FieldPath as InternalFieldPath } from '../model/path'; - -// The objects that are a part of this API are exposed to third-parties as -// compiled javascript so we want to flag our private members with a leading -// underscore to discourage their use. - -/** - * A `FieldPath` refers to a field in a document. The path may consist of a - * single field name (referring to a top-level field in the document), or a list - * of field names (referring to a nested field in the document). - */ -export class FieldPath implements PublicFieldPath, Compat { - readonly _delegate: ExpFieldPath; - /** - * Creates a FieldPath from the provided field names. If more than one field - * name is provided, the path will point to a nested field in a document. - * - * @param fieldNames - A list of field names. - */ - constructor(...fieldNames: string[]) { - this._delegate = new ExpFieldPath(...fieldNames); - } - - static documentId(): FieldPath { - /** - * Internal Note: The backend doesn't technically support querying by - * document ID. Instead it queries by the entire document name (full path - * included), but in the cases we currently support documentId(), the net - * effect is the same. - */ - return new FieldPath(InternalFieldPath.keyField().canonicalString()); - } - - isEqual(other: PublicFieldPath): boolean { - other = getModularInstance(other); - - if (!(other instanceof ExpFieldPath)) { - return false; - } - return this._delegate._internalPath.isEqual(other._internalPath); - } -} +export { FieldPath, documentId } from '../lite-api/field_path'; diff --git a/packages/firestore/src/api/field_value.ts b/packages/firestore/src/api/field_value.ts index 6e13c0e438b..d7d20d51cbb 100644 --- a/packages/firestore/src/api/field_value.ts +++ b/packages/firestore/src/api/field_value.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,52 +15,4 @@ * limitations under the License. */ -import { FieldValue as PublicFieldValue } from '@firebase/firestore-types'; -import { Compat } from '@firebase/util'; - -import { - arrayRemove, - arrayUnion, - deleteField, - FieldValue as FieldValue1, - increment, - serverTimestamp -} from '../../exp/index'; - -export class FieldValue implements PublicFieldValue, Compat { - static serverTimestamp(): FieldValue { - const delegate = serverTimestamp(); - delegate._methodName = 'FieldValue.serverTimestamp'; - return new FieldValue(delegate); - } - - static delete(): FieldValue { - const delegate = deleteField(); - delegate._methodName = 'FieldValue.delete'; - return new FieldValue(delegate); - } - - static arrayUnion(...elements: unknown[]): FieldValue { - const delegate = arrayUnion(...elements); - delegate._methodName = 'FieldValue.arrayUnion'; - return new FieldValue(delegate); - } - - static arrayRemove(...elements: unknown[]): FieldValue { - const delegate = arrayRemove(...elements); - delegate._methodName = 'FieldValue.arrayRemove'; - return new FieldValue(delegate); - } - - static increment(n: number): FieldValue { - const delegate = increment(n); - delegate._methodName = 'FieldValue.increment'; - return new FieldValue(delegate); - } - - constructor(readonly _delegate: FieldValue1) {} - - isEqual(other: FieldValue): boolean { - return this._delegate.isEqual(other._delegate); - } -} +export { FieldValue } from '../lite-api/field_value'; diff --git a/packages/firestore/src/exp/field_value_impl.ts b/packages/firestore/src/api/field_value_impl.ts similarity index 94% rename from packages/firestore/src/exp/field_value_impl.ts rename to packages/firestore/src/api/field_value_impl.ts index 0259fdd8e13..4689352f840 100644 --- a/packages/firestore/src/exp/field_value_impl.ts +++ b/packages/firestore/src/api/field_value_impl.ts @@ -21,4 +21,4 @@ export { arrayUnion, serverTimestamp, deleteField -} from '../lite/field_value_impl'; +} from '../lite-api/field_value_impl'; diff --git a/packages/firestore/src/api/geo_point.ts b/packages/firestore/src/api/geo_point.ts index a0709fabf30..672e7dffeff 100644 --- a/packages/firestore/src/api/geo_point.ts +++ b/packages/firestore/src/api/geo_point.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -export { GeoPoint } from '../../exp/index'; +export { GeoPoint } from '../lite-api/geo_point'; diff --git a/packages/firestore/src/exp/query.ts b/packages/firestore/src/api/query.ts similarity index 96% rename from packages/firestore/src/exp/query.ts rename to packages/firestore/src/api/query.ts index 6ba2d791075..b8a7cc90720 100644 --- a/packages/firestore/src/exp/query.ts +++ b/packages/firestore/src/api/query.ts @@ -29,4 +29,4 @@ export { query, QueryConstraint, QueryConstraintType -} from '../lite/query'; +} from '../lite-api/query'; diff --git a/packages/firestore/src/exp/reference.ts b/packages/firestore/src/api/reference.ts similarity index 96% rename from packages/firestore/src/exp/reference.ts rename to packages/firestore/src/api/reference.ts index 735075fae76..b9dd0b078cc 100644 --- a/packages/firestore/src/exp/reference.ts +++ b/packages/firestore/src/api/reference.ts @@ -29,4 +29,4 @@ export { WithFieldValue, PartialWithFieldValue, refEqual -} from '../lite/reference'; +} from '../lite-api/reference'; diff --git a/packages/firestore/src/exp/reference_impl.ts b/packages/firestore/src/api/reference_impl.ts similarity index 98% rename from packages/firestore/src/exp/reference_impl.ts rename to packages/firestore/src/api/reference_impl.ts index 53779d13313..dbefef93d26 100644 --- a/packages/firestore/src/exp/reference_impl.ts +++ b/packages/firestore/src/api/reference_impl.ts @@ -35,9 +35,9 @@ import { } from '../core/firestore_client'; import { newQueryForPath, Query as InternalQuery } from '../core/query'; import { ViewSnapshot } from '../core/view_snapshot'; -import { Bytes } from '../lite/bytes'; -import { FieldPath } from '../lite/field_path'; -import { validateHasExplicitOrderByForLimitToLast } from '../lite/query'; +import { Bytes } from '../lite-api/bytes'; +import { FieldPath } from '../lite-api/field_path'; +import { validateHasExplicitOrderByForLimitToLast } from '../lite-api/query'; import { CollectionReference, doc, @@ -47,16 +47,16 @@ import { SetOptions, UpdateData, WithFieldValue -} from '../lite/reference'; -import { applyFirestoreDataConverter } from '../lite/reference_impl'; +} from '../lite-api/reference'; +import { applyFirestoreDataConverter } from '../lite-api/reference_impl'; import { newUserDataReader, ParsedUpdateData, parseSetData, parseUpdateData, parseUpdateVarargs -} from '../lite/user_data_reader'; -import { AbstractUserDataWriter } from '../lite/user_data_writer'; +} from '../lite-api/user_data_reader'; +import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; import { DeleteMutation, Mutation, Precondition } from '../model/mutation'; import { debugAssert } from '../util/assert'; import { ByteString } from '../util/byte_string'; diff --git a/packages/firestore/src/exp/settings.ts b/packages/firestore/src/api/settings.ts similarity index 96% rename from packages/firestore/src/exp/settings.ts rename to packages/firestore/src/api/settings.ts index 83d87082301..f6e92854495 100644 --- a/packages/firestore/src/exp/settings.ts +++ b/packages/firestore/src/api/settings.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { FirestoreSettings as LiteSettings } from '../lite/settings'; +import { FirestoreSettings as LiteSettings } from '../lite-api/settings'; -export { DEFAULT_HOST } from '../lite/settings'; +export { DEFAULT_HOST } from '../lite-api/settings'; /** * Settings that can be passed to `enableIndexedDbPersistence()` to configure diff --git a/packages/firestore/src/exp/snapshot.ts b/packages/firestore/src/api/snapshot.ts similarity index 98% rename from packages/firestore/src/exp/snapshot.ts rename to packages/firestore/src/api/snapshot.ts index b10c721ebca..0a0f5ec0eda 100644 --- a/packages/firestore/src/exp/snapshot.ts +++ b/packages/firestore/src/api/snapshot.ts @@ -17,7 +17,7 @@ import { newQueryComparator } from '../core/query'; import { ChangeType, ViewSnapshot } from '../core/view_snapshot'; -import { FieldPath } from '../lite/field_path'; +import { FieldPath } from '../lite-api/field_path'; import { DocumentData, PartialWithFieldValue, @@ -25,14 +25,14 @@ import { queryEqual, SetOptions, WithFieldValue -} from '../lite/reference'; +} from '../lite-api/reference'; import { DocumentSnapshot as LiteDocumentSnapshot, fieldPathFromArgument, FirestoreDataConverter as LiteFirestoreDataConverter -} from '../lite/snapshot'; -import { UntypedFirestoreDataConverter } from '../lite/user_data_reader'; -import { AbstractUserDataWriter } from '../lite/user_data_writer'; +} from '../lite-api/snapshot'; +import { UntypedFirestoreDataConverter } from '../lite-api/user_data_reader'; +import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; import { Document } from '../model/document'; import { DocumentKey } from '../model/document_key'; import { debugAssert, fail } from '../util/assert'; diff --git a/packages/firestore/src/api/timestamp.ts b/packages/firestore/src/api/timestamp.ts index 959b2390297..58e12707560 100644 --- a/packages/firestore/src/api/timestamp.ts +++ b/packages/firestore/src/api/timestamp.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,4 @@ * limitations under the License. */ -export { Timestamp } from '../../exp/index'; +export { Timestamp } from '../lite-api/timestamp'; diff --git a/packages/firestore/src/exp/transaction.ts b/packages/firestore/src/api/transaction.ts similarity index 94% rename from packages/firestore/src/exp/transaction.ts rename to packages/firestore/src/api/transaction.ts index 802dc38101c..c16574fed66 100644 --- a/packages/firestore/src/exp/transaction.ts +++ b/packages/firestore/src/api/transaction.ts @@ -17,9 +17,9 @@ import { firestoreClientTransaction } from '../core/firestore_client'; import { Transaction as InternalTransaction } from '../core/transaction'; -import { DocumentReference } from '../lite/reference'; -import { Transaction as LiteTransaction } from '../lite/transaction'; -import { validateReference } from '../lite/write_batch'; +import { DocumentReference } from '../lite-api/reference'; +import { Transaction as LiteTransaction } from '../lite-api/transaction'; +import { validateReference } from '../lite-api/write_batch'; import { ensureFirestoreConfigured, Firestore } from './database'; import { ExpUserDataWriter } from './reference_impl'; diff --git a/packages/firestore/src/exp/write_batch.ts b/packages/firestore/src/api/write_batch.ts similarity index 96% rename from packages/firestore/src/exp/write_batch.ts rename to packages/firestore/src/api/write_batch.ts index 09f91e36bb7..0d7b6e2bde9 100644 --- a/packages/firestore/src/exp/write_batch.ts +++ b/packages/firestore/src/api/write_batch.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { WriteBatch } from '../lite/write_batch'; +import { WriteBatch } from '../lite-api/write_batch'; import { cast } from '../util/input_validation'; import { ensureFirestoreConfigured, Firestore } from './database'; diff --git a/packages/firestore/src/config.ts b/packages/firestore/src/config.ts deleted file mode 100644 index 22866845806..00000000000 --- a/packages/firestore/src/config.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp, FirebaseNamespace } from '@firebase/app-types'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { Component, ComponentType, Provider } from '@firebase/component'; - -import { - CACHE_SIZE_UNLIMITED, - CollectionReference, - DocumentReference, - DocumentSnapshot, - Firestore, - Query, - QueryDocumentSnapshot, - QuerySnapshot, - Transaction, - WriteBatch, - setLogLevel, - Blob, - FieldPath, - GeoPoint, - Timestamp, - FieldValue -} from '../export'; - -const firestoreNamespace = { - Firestore, - GeoPoint, - Timestamp, - Blob, - Transaction, - WriteBatch, - DocumentReference, - DocumentSnapshot, - Query, - QueryDocumentSnapshot, - QuerySnapshot, - CollectionReference, - FieldPath, - FieldValue, - setLogLevel, - CACHE_SIZE_UNLIMITED -}; - -/** - * Configures Firestore as part of the Firebase SDK by calling registerService. - * - * @param firebase - The FirebaseNamespace to register Firestore with - * @param firestoreFactory - A factory function that returns a new Firestore - * instance. - */ -export function configureForFirebase( - firebase: FirebaseNamespace, - firestoreFactory: ( - app: FirebaseApp, - auth: Provider - ) => Firestore -): void { - (firebase as _FirebaseNamespace).INTERNAL.registerComponent( - new Component( - 'firestore', - container => { - const app = container.getProvider('app').getImmediate()!; - return firestoreFactory(app, container.getProvider('auth-internal')); - }, - ComponentType.PUBLIC - ).setServiceProps({ ...firestoreNamespace }) - ); -} diff --git a/packages/firestore/src/core/component_provider.ts b/packages/firestore/src/core/component_provider.ts index 2bf13fc33e0..9c847ffd34c 100644 --- a/packages/firestore/src/core/component_provider.ts +++ b/packages/firestore/src/core/component_provider.ts @@ -187,7 +187,7 @@ export class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentPro // set it after localStore / remoteStore are started. await this.persistence.setPrimaryStateListener(() => { if (this.gcScheduler && !this.gcScheduler.started) { - this.gcScheduler.start(this.localStore); + this.gcScheduler.start(this.localStore); } return Promise.resolve(); }); diff --git a/packages/firestore/src/core/database_info.ts b/packages/firestore/src/core/database_info.ts index 218e0626d80..d88a8b7fc2d 100644 --- a/packages/firestore/src/core/database_info.ts +++ b/packages/firestore/src/core/database_info.ts @@ -48,7 +48,10 @@ export class DatabaseInfo { /** The default database name for a project. */ const DEFAULT_DATABASE_NAME = '(default)'; -/** Represents the database ID a Firestore client is associated with. */ +/** + * Represents the database ID a Firestore client is associated with. + * @internal + */ export class DatabaseId { readonly database: string; constructor(readonly projectId: string, database?: string) { diff --git a/packages/firestore/src/core/firestore_client.ts b/packages/firestore/src/core/firestore_client.ts index 575c2ceb5a0..d8e5b04840a 100644 --- a/packages/firestore/src/core/firestore_client.ts +++ b/packages/firestore/src/core/firestore_client.ts @@ -17,12 +17,12 @@ import { GetOptions } from '@firebase/firestore-types'; +import { LoadBundleTask } from '../api/bundle'; import { CredentialChangeListener, CredentialsProvider } from '../api/credentials'; import { User } from '../auth/user'; -import { LoadBundleTask } from '../exp/bundle'; import { LocalStore } from '../local/local_store'; import { localStoreExecuteQuery, @@ -116,7 +116,7 @@ export class FirestoreClient { public asyncQueue: AsyncQueue, private databaseInfo: DatabaseInfo ) { - this.credentials.setChangeListener(asyncQueue, async user => { + this.credentials.start(asyncQueue, async user => { logDebug(LOG_TAG, 'Received user=', user.uid); await this.credentialListener(user); this.user = user; @@ -163,10 +163,10 @@ export class FirestoreClient { await this.offlineComponents.terminate(); } - // `removeChangeListener` must be called after shutting down the - // RemoteStore as it will prevent the RemoteStore from retrieving - // auth tokens. - this.credentials.removeChangeListener(); + // The credentials provider must be terminated after shutting down the + // RemoteStore as it will prevent the RemoteStore from retrieving auth + // tokens. + this.credentials.shutdown(); deferred.resolve(); } catch (e) { const firestoreError = wrapInUserErrorIfRecoverable( diff --git a/packages/firestore/src/core/snapshot_version.ts b/packages/firestore/src/core/snapshot_version.ts index df6bf0c841e..9f2e5581a7f 100644 --- a/packages/firestore/src/core/snapshot_version.ts +++ b/packages/firestore/src/core/snapshot_version.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; /** * A version of a document in Firestore. This corresponds to the version diff --git a/packages/firestore/src/core/sync_engine_impl.ts b/packages/firestore/src/core/sync_engine_impl.ts index da272ec0ac7..1eb42cca1ba 100644 --- a/packages/firestore/src/core/sync_engine_impl.ts +++ b/packages/firestore/src/core/sync_engine_impl.ts @@ -15,8 +15,8 @@ * limitations under the License. */ +import { LoadBundleTask } from '../api/bundle'; import { User } from '../auth/user'; -import { LoadBundleTask } from '../exp/bundle'; import { ignoreIfPrimaryLeaseLoss, LocalStore } from '../local/local_store'; import { localStoreAcknowledgeBatch, diff --git a/packages/firestore/src/core/transaction.ts b/packages/firestore/src/core/transaction.ts index 760e4174f6b..f50cde580b4 100644 --- a/packages/firestore/src/core/transaction.ts +++ b/packages/firestore/src/core/transaction.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { ParsedSetData, ParsedUpdateData } from '../lite/user_data_reader'; +import { ParsedSetData, ParsedUpdateData } from '../lite-api/user_data_reader'; import { Document } from '../model/document'; import { DocumentKey } from '../model/document_key'; import { diff --git a/packages/firestore/src/exp/database.ts b/packages/firestore/src/exp/database.ts deleted file mode 100644 index e49d8870897..00000000000 --- a/packages/firestore/src/exp/database.ts +++ /dev/null @@ -1,546 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line import/no-extraneous-dependencies -import { - _getProvider, - _removeServiceInstance, - FirebaseApp, - getApp -} from '@firebase/app-exp'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { Provider } from '@firebase/component'; -import { deepEqual } from '@firebase/util'; - -import { - IndexedDbOfflineComponentProvider, - MultiTabOfflineComponentProvider, - OfflineComponentProvider, - OnlineComponentProvider -} from '../core/component_provider'; -import { DatabaseId } from '../core/database_info'; -import { - FirestoreClient, - firestoreClientDisableNetwork, - firestoreClientEnableNetwork, - firestoreClientGetNamedQuery, - firestoreClientLoadBundle, - firestoreClientWaitForPendingWrites, - setOfflineComponentProvider, - setOnlineComponentProvider -} from '../core/firestore_client'; -import { makeDatabaseInfo } from '../lite/components'; -import { Firestore as LiteFirestore } from '../lite/database'; -import { Query } from '../lite/reference'; -import { - indexedDbClearPersistence, - indexedDbStoragePrefix -} from '../local/indexeddb_persistence'; -import { LRU_COLLECTION_DISABLED } from '../local/lru_garbage_collector'; -import { LRU_MINIMUM_CACHE_SIZE_BYTES } from '../local/lru_garbage_collector_impl'; -import { debugAssert } from '../util/assert'; -import { AsyncQueue } from '../util/async_queue'; -import { newAsyncQueue } from '../util/async_queue_impl'; -import { Code, FirestoreError } from '../util/error'; -import { cast } from '../util/input_validation'; -import { Deferred } from '../util/promise'; - -import { LoadBundleTask } from './bundle'; -import { PersistenceSettings, FirestoreSettings } from './settings'; -export { connectFirestoreEmulator } from '../lite/database'; - -/** DOMException error code constants. */ -const DOM_EXCEPTION_INVALID_STATE = 11; -const DOM_EXCEPTION_ABORTED = 20; -const DOM_EXCEPTION_QUOTA_EXCEEDED = 22; - -/** - * Constant used to indicate the LRU garbage collection should be disabled. - * Set this value as the `cacheSizeBytes` on the settings passed to the - * {@link Firestore} instance. - */ -export const CACHE_SIZE_UNLIMITED = LRU_COLLECTION_DISABLED; - -/** - * The Cloud Firestore service interface. - * - * Do not call this constructor directly. Instead, use {@link getFirestore}. - */ -export class Firestore extends LiteFirestore { - /** - * Whether it's a {@link Firestore} or Firestore Lite instance. - */ - type: 'firestore-lite' | 'firestore' = 'firestore'; - - readonly _queue: AsyncQueue = newAsyncQueue(); - readonly _persistenceKey: string; - - _firestoreClient: FirestoreClient | undefined; - - /** @hideconstructor */ - constructor( - databaseIdOrApp: DatabaseId | FirebaseApp, - authProvider: Provider - ) { - super(databaseIdOrApp, authProvider); - this._persistenceKey = - 'name' in databaseIdOrApp ? databaseIdOrApp.name : '[DEFAULT]'; - } - - _terminate(): Promise { - if (!this._firestoreClient) { - // The client must be initialized to ensure that all subsequent API - // usage throws an exception. - configureFirestore(this); - } - return this._firestoreClient!.terminate(); - } -} - -/** - * Initializes a new instance of {@link Firestore} with the provided settings. - * Can only be called before any other function, including - * {@link getFirestore}. If the custom settings are empty, this function is - * equivalent to calling {@link getFirestore}. - * - * @param app - The {@link @firebase/app#FirebaseApp} with which the {@link Firestore} instance will - * be associated. - * @param settings - A settings object to configure the {@link Firestore} instance. - * @returns A newly initialized {@link Firestore} instance. - */ -export function initializeFirestore( - app: FirebaseApp, - settings: FirestoreSettings -): Firestore { - const provider = _getProvider(app, 'firestore-exp'); - - if (provider.isInitialized()) { - const existingInstance = provider.getImmediate(); - const initialSettings = provider.getOptions() as FirestoreSettings; - if (deepEqual(initialSettings, settings)) { - return existingInstance; - } else { - throw new FirestoreError( - Code.FAILED_PRECONDITION, - 'initializeFirestore() has already been called with ' + - 'different options. To avoid this error, call initializeFirestore() with the ' + - 'same options as when it was originally called, or call getFirestore() to return the' + - ' already initialized instance.' - ); - } - } - - if ( - settings.cacheSizeBytes !== undefined && - settings.cacheSizeBytes !== CACHE_SIZE_UNLIMITED && - settings.cacheSizeBytes < LRU_MINIMUM_CACHE_SIZE_BYTES - ) { - throw new FirestoreError( - Code.INVALID_ARGUMENT, - `cacheSizeBytes must be at least ${LRU_MINIMUM_CACHE_SIZE_BYTES}` - ); - } - - return provider.initialize({ options: settings }); -} - -/** - * Returns the existing {@link Firestore} instance that is associated with the - * provided {@link @firebase/app#FirebaseApp}. If no instance exists, initializes a new - * instance with default settings. - * - * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned {@link Firestore} - * instance is associated with. - * @returns The {@link Firestore} instance of the provided app. - */ -export function getFirestore(app: FirebaseApp = getApp()): Firestore { - return _getProvider(app, 'firestore-exp').getImmediate() as Firestore; -} - -/** - * @internal - */ -export function ensureFirestoreConfigured( - firestore: Firestore -): FirestoreClient { - if (!firestore._firestoreClient) { - configureFirestore(firestore); - } - firestore._firestoreClient!.verifyNotTerminated(); - return firestore._firestoreClient as FirestoreClient; -} - -export function configureFirestore(firestore: Firestore): void { - const settings = firestore._freezeSettings(); - debugAssert(!!settings.host, 'FirestoreSettings.host is not set'); - debugAssert( - !firestore._firestoreClient, - 'configureFirestore() called multiple times' - ); - - const databaseInfo = makeDatabaseInfo( - firestore._databaseId, - firestore._app?.options.appId || '', - firestore._persistenceKey, - settings - ); - firestore._firestoreClient = new FirestoreClient( - firestore._credentials, - firestore._queue, - databaseInfo - ); -} - -/** - * Attempts to enable persistent storage, if possible. - * - * Must be called before any other functions (other than - * {@link initializeFirestore}, {@link getFirestore} or - * {@link clearIndexedDbPersistence}. - * - * If this fails, `enableIndexedDbPersistence()` will reject the promise it - * returns. Note that even after this failure, the {@link Firestore} instance will - * remain usable, however offline persistence will be disabled. - * - * There are several reasons why this can fail, which can be identified by - * the `code` on the error. - * - * * failed-precondition: The app is already open in another browser tab. - * * unimplemented: The browser is incompatible with the offline - * persistence implementation. - * - * @param firestore - The {@link Firestore} instance to enable persistence for. - * @param persistenceSettings - Optional settings object to configure - * persistence. - * @returns A `Promise` that represents successfully enabling persistent storage. - */ -export function enableIndexedDbPersistence( - firestore: Firestore, - persistenceSettings?: PersistenceSettings -): Promise { - firestore = cast(firestore, Firestore); - verifyNotInitialized(firestore); - - const client = ensureFirestoreConfigured(firestore); - const settings = firestore._freezeSettings(); - - const onlineComponentProvider = new OnlineComponentProvider(); - const offlineComponentProvider = new IndexedDbOfflineComponentProvider( - onlineComponentProvider, - settings.cacheSizeBytes, - persistenceSettings?.forceOwnership - ); - return setPersistenceProviders( - client, - onlineComponentProvider, - offlineComponentProvider - ); -} - -/** - * Attempts to enable multi-tab persistent storage, if possible. If enabled - * across all tabs, all operations share access to local persistence, including - * shared execution of queries and latency-compensated local document updates - * across all connected instances. - * - * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise - * it returns. Note that even after this failure, the {@link Firestore} instance will - * remain usable, however offline persistence will be disabled. - * - * There are several reasons why this can fail, which can be identified by - * the `code` on the error. - * - * * failed-precondition: The app is already open in another browser tab and - * multi-tab is not enabled. - * * unimplemented: The browser is incompatible with the offline - * persistence implementation. - * - * @param firestore - The {@link Firestore} instance to enable persistence for. - * @returns A `Promise` that represents successfully enabling persistent - * storage. - */ -export function enableMultiTabIndexedDbPersistence( - firestore: Firestore -): Promise { - firestore = cast(firestore, Firestore); - verifyNotInitialized(firestore); - - const client = ensureFirestoreConfigured(firestore); - const settings = firestore._freezeSettings(); - - const onlineComponentProvider = new OnlineComponentProvider(); - const offlineComponentProvider = new MultiTabOfflineComponentProvider( - onlineComponentProvider, - settings.cacheSizeBytes - ); - return setPersistenceProviders( - client, - onlineComponentProvider, - offlineComponentProvider - ); -} - -/** - * Registers both the `OfflineComponentProvider` and `OnlineComponentProvider`. - * If the operation fails with a recoverable error (see - * `canRecoverFromIndexedDbError()` below), the returned Promise is rejected - * but the client remains usable. - */ -function setPersistenceProviders( - client: FirestoreClient, - onlineComponentProvider: OnlineComponentProvider, - offlineComponentProvider: OfflineComponentProvider -): Promise { - const persistenceResult = new Deferred(); - return client.asyncQueue - .enqueue(async () => { - try { - await setOfflineComponentProvider(client, offlineComponentProvider); - await setOnlineComponentProvider(client, onlineComponentProvider); - persistenceResult.resolve(); - } catch (e) { - if (!canFallbackFromIndexedDbError(e)) { - throw e; - } - console.warn( - 'Error enabling offline persistence. Falling back to ' + - 'persistence disabled: ' + - e - ); - persistenceResult.reject(e); - } - }) - .then(() => persistenceResult.promise); -} - -/** - * Decides whether the provided error allows us to gracefully disable - * persistence (as opposed to crashing the client). - */ -function canFallbackFromIndexedDbError( - error: FirestoreError | DOMException -): boolean { - if (error.name === 'FirebaseError') { - return ( - error.code === Code.FAILED_PRECONDITION || - error.code === Code.UNIMPLEMENTED - ); - } else if ( - typeof DOMException !== 'undefined' && - error instanceof DOMException - ) { - // There are a few known circumstances where we can open IndexedDb but - // trying to read/write will fail (e.g. quota exceeded). For - // well-understood cases, we attempt to detect these and then gracefully - // fall back to memory persistence. - // NOTE: Rather than continue to add to this list, we could decide to - // always fall back, with the risk that we might accidentally hide errors - // representing actual SDK bugs. - return ( - // When the browser is out of quota we could get either quota exceeded - // or an aborted error depending on whether the error happened during - // schema migration. - error.code === DOM_EXCEPTION_QUOTA_EXCEEDED || - error.code === DOM_EXCEPTION_ABORTED || - // Firefox Private Browsing mode disables IndexedDb and returns - // INVALID_STATE for any usage. - error.code === DOM_EXCEPTION_INVALID_STATE - ); - } - - return true; -} - -/** - * Clears the persistent storage. This includes pending writes and cached - * documents. - * - * Must be called while the {@link Firestore} instance is not started (after the app is - * terminated or when the app is first initialized). On startup, this function - * must be called before other functions (other than {@link - * initializeFirestore} or {@link getFirestore})). If the {@link Firestore} - * instance is still running, the promise will be rejected with the error code - * of `failed-precondition`. - * - * Note: `clearIndexedDbPersistence()` is primarily intended to help write - * reliable tests that use Cloud Firestore. It uses an efficient mechanism for - * dropping existing data but does not attempt to securely overwrite or - * otherwise make cached data unrecoverable. For applications that are sensitive - * to the disclosure of cached data in between user sessions, we strongly - * recommend not enabling persistence at all. - * - * @param firestore - The {@link Firestore} instance to clear persistence for. - * @returns A `Promise` that is resolved when the persistent storage is - * cleared. Otherwise, the promise is rejected with an error. - */ -export function clearIndexedDbPersistence(firestore: Firestore): Promise { - if (firestore._initialized && !firestore._terminated) { - throw new FirestoreError( - Code.FAILED_PRECONDITION, - 'Persistence can only be cleared before a Firestore instance is ' + - 'initialized or after it is terminated.' - ); - } - - const deferred = new Deferred(); - firestore._queue.enqueueAndForgetEvenWhileRestricted(async () => { - try { - await indexedDbClearPersistence( - indexedDbStoragePrefix(firestore._databaseId, firestore._persistenceKey) - ); - deferred.resolve(); - } catch (e) { - deferred.reject(e); - } - }); - return deferred.promise; -} - -/** - * Waits until all currently pending writes for the active user have been - * acknowledged by the backend. - * - * The returned promise resolves immediately if there are no outstanding writes. - * Otherwise, the promise waits for all previously issued writes (including - * those written in a previous app session), but it does not wait for writes - * that were added after the function is called. If you want to wait for - * additional writes, call `waitForPendingWrites()` again. - * - * Any outstanding `waitForPendingWrites()` promises are rejected during user - * changes. - * - * @returns A `Promise` which resolves when all currently pending writes have been - * acknowledged by the backend. - */ -export function waitForPendingWrites(firestore: Firestore): Promise { - firestore = cast(firestore, Firestore); - const client = ensureFirestoreConfigured(firestore); - return firestoreClientWaitForPendingWrites(client); -} - -/** - * Re-enables use of the network for this {@link Firestore} instance after a prior - * call to {@link disableNetwork}. - * - * @returns A `Promise` that is resolved once the network has been enabled. - */ -export function enableNetwork(firestore: Firestore): Promise { - firestore = cast(firestore, Firestore); - const client = ensureFirestoreConfigured(firestore); - return firestoreClientEnableNetwork(client); -} - -/** - * Disables network usage for this instance. It can be re-enabled via {@link - * enableNetwork}. While the network is disabled, any snapshot listeners, - * `getDoc()` or `getDocs()` calls will return results from cache, and any write - * operations will be queued until the network is restored. - * - * @returns A `Promise` that is resolved once the network has been disabled. - */ -export function disableNetwork(firestore: Firestore): Promise { - firestore = cast(firestore, Firestore); - const client = ensureFirestoreConfigured(firestore); - return firestoreClientDisableNetwork(client); -} - -/** - * Terminates the provided {@link Firestore} instance. - * - * After calling `terminate()` only the `clearIndexedDbPersistence()` function - * may be used. Any other function will throw a `FirestoreError`. - * - * To restart after termination, create a new instance of FirebaseFirestore with - * {@link getFirestore}. - * - * Termination does not cancel any pending writes, and any promises that are - * awaiting a response from the server will not be resolved. If you have - * persistence enabled, the next time you start this instance, it will resume - * sending these writes to the server. - * - * Note: Under normal circumstances, calling `terminate()` is not required. This - * function is useful only when you want to force this instance to release all - * of its resources or in combination with `clearIndexedDbPersistence()` to - * ensure that all local state is destroyed between test runs. - * - * @returns A `Promise` that is resolved when the instance has been successfully - * terminated. - */ -export function terminate(firestore: Firestore): Promise { - _removeServiceInstance(firestore.app, 'firestore-exp'); - return firestore._delete(); -} - -/** - * Loads a Firestore bundle into the local cache. - * - * @param firestore - The {@link Firestore} instance to load bundles for for. - * @param bundleData - An object representing the bundle to be loaded. Valid objects are - * `ArrayBuffer`, `ReadableStream` or `string`. - * - * @returns - * A `LoadBundleTask` object, which notifies callers with progress updates, and completion - * or error events. It can be used as a `Promise`. - */ -export function loadBundle( - firestore: Firestore, - bundleData: ReadableStream | ArrayBuffer | string -): LoadBundleTask { - firestore = cast(firestore, Firestore); - const client = ensureFirestoreConfigured(firestore); - const resultTask = new LoadBundleTask(); - firestoreClientLoadBundle( - client, - firestore._databaseId, - bundleData, - resultTask - ); - return resultTask; -} - -/** - * Reads a Firestore {@link Query} from local cache, identified by the given name. - * - * The named queries are packaged into bundles on the server side (along - * with resulting documents), and loaded to local cache using `loadBundle`. Once in local - * cache, use this method to extract a {@link Query} by name. - */ -export function namedQuery( - firestore: Firestore, - name: string -): Promise { - firestore = cast(firestore, Firestore); - const client = ensureFirestoreConfigured(firestore); - return firestoreClientGetNamedQuery(client, name).then(namedQuery => { - if (!namedQuery) { - return null; - } - - return new Query(firestore, null, namedQuery.query); - }); -} - -function verifyNotInitialized(firestore: Firestore): void { - if (firestore._initialized || firestore._terminated) { - throw new FirestoreError( - Code.FAILED_PRECONDITION, - 'Firestore has already been started and persistence can no longer be ' + - 'enabled. You can only enable persistence before calling any other ' + - 'methods on a Firestore object.' - ); - } -} diff --git a/packages/firestore/src/exp/field_path.ts b/packages/firestore/src/exp/field_path.ts deleted file mode 100644 index 04047bfa792..00000000000 --- a/packages/firestore/src/exp/field_path.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { FieldPath, documentId } from '../lite/field_path'; diff --git a/packages/firestore/src/exp/field_value.ts b/packages/firestore/src/exp/field_value.ts deleted file mode 100644 index 9ebc601869c..00000000000 --- a/packages/firestore/src/exp/field_value.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { FieldValue } from '../lite/field_value'; diff --git a/packages/firestore/exp/index.node.ts b/packages/firestore/src/index.node.ts similarity index 100% rename from packages/firestore/exp/index.node.ts rename to packages/firestore/src/index.node.ts diff --git a/packages/firestore/exp/index.rn.ts b/packages/firestore/src/index.rn.ts similarity index 100% rename from packages/firestore/exp/index.rn.ts rename to packages/firestore/src/index.rn.ts diff --git a/packages/firestore/exp/index.ts b/packages/firestore/src/index.ts similarity index 83% rename from packages/firestore/exp/index.ts rename to packages/firestore/src/index.ts index aab859b67a7..5babf0ef726 100644 --- a/packages/firestore/exp/index.ts +++ b/packages/firestore/src/index.ts @@ -21,8 +21,15 @@ * limitations under the License. */ +import { Firestore } from './api/database'; import { registerFirestore } from './register'; registerFirestore(); export * from './api'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'firestore': Firestore; + } +} diff --git a/packages/firestore/src/lite/bytes.ts b/packages/firestore/src/lite-api/bytes.ts similarity index 100% rename from packages/firestore/src/lite/bytes.ts rename to packages/firestore/src/lite-api/bytes.ts diff --git a/packages/firestore/src/lite/components.ts b/packages/firestore/src/lite-api/components.ts similarity index 98% rename from packages/firestore/src/lite/components.ts rename to packages/firestore/src/lite-api/components.ts index 24179eaf319..8dfaa32d1b9 100644 --- a/packages/firestore/src/lite/components.ts +++ b/packages/firestore/src/lite-api/components.ts @@ -16,7 +16,7 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -import { _FirebaseService } from '@firebase/app-exp'; +import { _FirebaseService } from '@firebase/app'; import { CredentialsProvider } from '../api/credentials'; import { DatabaseId, DatabaseInfo } from '../core/database_info'; diff --git a/packages/firestore/src/lite/database.ts b/packages/firestore/src/lite-api/database.ts similarity index 95% rename from packages/firestore/src/lite/database.ts rename to packages/firestore/src/lite-api/database.ts index 536877fe672..a082af975d3 100644 --- a/packages/firestore/src/lite/database.ts +++ b/packages/firestore/src/lite-api/database.ts @@ -21,16 +21,12 @@ import { _removeServiceInstance, FirebaseApp, getApp -} from '@firebase/app-exp'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { Provider } from '@firebase/component'; +} from '@firebase/app'; import { createMockUserToken, EmulatorMockTokenOptions } from '@firebase/util'; import { CredentialsProvider, - EmptyCredentialsProvider, EmulatorCredentialsProvider, - FirebaseCredentialsProvider, makeCredentialsProvider, OAuthToken } from '../api/credentials'; @@ -67,7 +63,6 @@ export class Firestore implements FirestoreService { readonly _databaseId: DatabaseId; readonly _persistenceKey: string = '(lite)'; - _credentials: CredentialsProvider; private _settings = new FirestoreSettingsImpl({}); private _settingsFrozen = false; @@ -81,15 +76,13 @@ export class Firestore implements FirestoreService { /** @hideconstructor */ constructor( databaseIdOrApp: DatabaseId | FirebaseApp, - authProvider: Provider + public _credentials: CredentialsProvider ) { if (databaseIdOrApp instanceof DatabaseId) { this._databaseId = databaseIdOrApp; - this._credentials = new EmptyCredentialsProvider(); } else { this._app = databaseIdOrApp as FirebaseApp; this._databaseId = databaseIdFromApp(databaseIdOrApp as FirebaseApp); - this._credentials = new FirebaseCredentialsProvider(authProvider); } } @@ -220,6 +213,7 @@ export function getFirestore(app: FirebaseApp = getApp()): Firestore { return _getProvider(app, 'firestore/lite').getImmediate() as Firestore; } +export { EmulatorMockTokenOptions } from '@firebase/util'; /** * Modify this instance to communicate with the Cloud Firestore emulator. * diff --git a/packages/firestore/src/lite/field_path.ts b/packages/firestore/src/lite-api/field_path.ts similarity index 100% rename from packages/firestore/src/lite/field_path.ts rename to packages/firestore/src/lite-api/field_path.ts diff --git a/packages/firestore/src/lite/field_value.ts b/packages/firestore/src/lite-api/field_value.ts similarity index 100% rename from packages/firestore/src/lite/field_value.ts rename to packages/firestore/src/lite-api/field_value.ts diff --git a/packages/firestore/src/lite/field_value_impl.ts b/packages/firestore/src/lite-api/field_value_impl.ts similarity index 100% rename from packages/firestore/src/lite/field_value_impl.ts rename to packages/firestore/src/lite-api/field_value_impl.ts diff --git a/packages/firestore/src/lite/geo_point.ts b/packages/firestore/src/lite-api/geo_point.ts similarity index 100% rename from packages/firestore/src/lite/geo_point.ts rename to packages/firestore/src/lite-api/geo_point.ts diff --git a/packages/firestore/src/lite/query.ts b/packages/firestore/src/lite-api/query.ts similarity index 100% rename from packages/firestore/src/lite/query.ts rename to packages/firestore/src/lite-api/query.ts diff --git a/packages/firestore/src/lite/reference.ts b/packages/firestore/src/lite-api/reference.ts similarity index 100% rename from packages/firestore/src/lite/reference.ts rename to packages/firestore/src/lite-api/reference.ts diff --git a/packages/firestore/src/lite/reference_impl.ts b/packages/firestore/src/lite-api/reference_impl.ts similarity index 100% rename from packages/firestore/src/lite/reference_impl.ts rename to packages/firestore/src/lite-api/reference_impl.ts diff --git a/packages/firestore/src/lite/settings.ts b/packages/firestore/src/lite-api/settings.ts similarity index 100% rename from packages/firestore/src/lite/settings.ts rename to packages/firestore/src/lite-api/settings.ts diff --git a/packages/firestore/src/lite/snapshot.ts b/packages/firestore/src/lite-api/snapshot.ts similarity index 100% rename from packages/firestore/src/lite/snapshot.ts rename to packages/firestore/src/lite-api/snapshot.ts diff --git a/packages/firestore/src/lite/timestamp.ts b/packages/firestore/src/lite-api/timestamp.ts similarity index 100% rename from packages/firestore/src/lite/timestamp.ts rename to packages/firestore/src/lite-api/timestamp.ts diff --git a/packages/firestore/src/lite/transaction.ts b/packages/firestore/src/lite-api/transaction.ts similarity index 100% rename from packages/firestore/src/lite/transaction.ts rename to packages/firestore/src/lite-api/transaction.ts diff --git a/packages/firestore/src/lite/types.ts b/packages/firestore/src/lite-api/types.ts similarity index 100% rename from packages/firestore/src/lite/types.ts rename to packages/firestore/src/lite-api/types.ts diff --git a/packages/firestore/src/lite/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts similarity index 100% rename from packages/firestore/src/lite/user_data_reader.ts rename to packages/firestore/src/lite-api/user_data_reader.ts diff --git a/packages/firestore/src/lite/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts similarity index 98% rename from packages/firestore/src/lite/user_data_writer.ts rename to packages/firestore/src/lite-api/user_data_writer.ts index 2b30b970573..7f2e2b86301 100644 --- a/packages/firestore/src/lite/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -18,8 +18,6 @@ import { DocumentData } from '@firebase/firestore-types'; import { DatabaseId } from '../core/database_info'; -import { GeoPoint } from '../lite/geo_point'; -import { Timestamp } from '../lite/timestamp'; import { DocumentKey } from '../model/document_key'; import { normalizeByteString, @@ -46,6 +44,9 @@ import { ByteString } from '../util/byte_string'; import { logError } from '../util/log'; import { forEach } from '../util/obj'; +import { GeoPoint } from './geo_point'; +import { Timestamp } from './timestamp'; + export type ServerTimestampBehavior = 'estimate' | 'previous' | 'none'; /** diff --git a/packages/firestore/src/lite/write_batch.ts b/packages/firestore/src/lite-api/write_batch.ts similarity index 100% rename from packages/firestore/src/lite/write_batch.ts rename to packages/firestore/src/lite-api/write_batch.ts diff --git a/packages/firestore/src/local/indexeddb_mutation_queue.ts b/packages/firestore/src/local/indexeddb_mutation_queue.ts index 5ceb0d9943d..cb9b8dd21ee 100644 --- a/packages/firestore/src/local/indexeddb_mutation_queue.ts +++ b/packages/firestore/src/local/indexeddb_mutation_queue.ts @@ -18,7 +18,7 @@ import { User } from '../auth/user'; import { isCollectionGroupQuery, isDocumentQuery, Query } from '../core/query'; import { BatchId } from '../core/types'; -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { DocumentKeySet } from '../model/collections'; import { DocumentKey } from '../model/document_key'; import { Mutation } from '../model/mutation'; diff --git a/packages/firestore/src/local/indexeddb_target_cache.ts b/packages/firestore/src/local/indexeddb_target_cache.ts index 39d268ec0e4..c864e5afde7 100644 --- a/packages/firestore/src/local/indexeddb_target_cache.ts +++ b/packages/firestore/src/local/indexeddb_target_cache.ts @@ -19,7 +19,7 @@ import { SnapshotVersion } from '../core/snapshot_version'; import { canonifyTarget, Target, targetEquals } from '../core/target'; import { TargetIdGenerator } from '../core/target_id_generator'; import { ListenSequenceNumber, TargetId } from '../core/types'; -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { DocumentKeySet, documentKeySet } from '../model/collections'; import { DocumentKey } from '../model/document_key'; import { hardAssert } from '../util/assert'; diff --git a/packages/firestore/src/local/local_serializer.ts b/packages/firestore/src/local/local_serializer.ts index 290a5664a36..dabb8c24c58 100644 --- a/packages/firestore/src/local/local_serializer.ts +++ b/packages/firestore/src/local/local_serializer.ts @@ -15,11 +15,11 @@ * limitations under the License. */ +import { Timestamp } from '../api/timestamp'; import { BundleMetadata, NamedQuery } from '../core/bundle'; import { LimitType, Query, queryWithLimit } from '../core/query'; import { SnapshotVersion } from '../core/snapshot_version'; import { canonifyTarget, isDocumentTarget, Target } from '../core/target'; -import { Timestamp } from '../exp/timestamp'; import { MutableDocument } from '../model/document'; import { DocumentKey } from '../model/document_key'; import { MutationBatch } from '../model/mutation_batch'; diff --git a/packages/firestore/src/local/local_store_impl.ts b/packages/firestore/src/local/local_store_impl.ts index f34385f50b9..9cb0a3ea9bc 100644 --- a/packages/firestore/src/local/local_store_impl.ts +++ b/packages/firestore/src/local/local_store_impl.ts @@ -21,7 +21,7 @@ import { newQueryForPath, Query, queryToTarget } from '../core/query'; import { SnapshotVersion } from '../core/snapshot_version'; import { canonifyTarget, Target, targetEquals } from '../core/target'; import { BatchId, TargetId } from '../core/types'; -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { documentKeySet, DocumentKeySet, diff --git a/packages/firestore/src/local/memory_mutation_queue.ts b/packages/firestore/src/local/memory_mutation_queue.ts index 490f6b9175d..91f51d8c5d6 100644 --- a/packages/firestore/src/local/memory_mutation_queue.ts +++ b/packages/firestore/src/local/memory_mutation_queue.ts @@ -17,7 +17,7 @@ import { isCollectionGroupQuery, Query } from '../core/query'; import { BatchId } from '../core/types'; -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { DocumentKey } from '../model/document_key'; import { Mutation } from '../model/mutation'; import { MutationBatch } from '../model/mutation_batch'; diff --git a/packages/firestore/src/local/mutation_queue.ts b/packages/firestore/src/local/mutation_queue.ts index 04ce858b9d6..299ad3d6113 100644 --- a/packages/firestore/src/local/mutation_queue.ts +++ b/packages/firestore/src/local/mutation_queue.ts @@ -17,7 +17,7 @@ import { Query } from '../core/query'; import { BatchId } from '../core/types'; -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { DocumentKey } from '../model/document_key'; import { Mutation } from '../model/mutation'; import { MutationBatch } from '../model/mutation_batch'; diff --git a/packages/firestore/src/model/document_key.ts b/packages/firestore/src/model/document_key.ts index 989890c185d..e40b7c0c565 100644 --- a/packages/firestore/src/model/document_key.ts +++ b/packages/firestore/src/model/document_key.ts @@ -19,6 +19,9 @@ import { debugAssert } from '../util/assert'; import { ResourcePath } from './path'; +/** + * @internal + */ export class DocumentKey { constructor(readonly path: ResourcePath) { debugAssert( diff --git a/packages/firestore/src/model/mutation.ts b/packages/firestore/src/model/mutation.ts index eecac8048cf..5d04eea4be8 100644 --- a/packages/firestore/src/model/mutation.ts +++ b/packages/firestore/src/model/mutation.ts @@ -16,7 +16,7 @@ */ import { SnapshotVersion } from '../core/snapshot_version'; -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { Value as ProtoValue } from '../protos/firestore_proto_api'; import { debugAssert, hardAssert } from '../util/assert'; import { arrayEquals } from '../util/misc'; diff --git a/packages/firestore/src/model/mutation_batch.ts b/packages/firestore/src/model/mutation_batch.ts index 49ab8b21176..b05b2c7cfb2 100644 --- a/packages/firestore/src/model/mutation_batch.ts +++ b/packages/firestore/src/model/mutation_batch.ts @@ -17,7 +17,7 @@ import { SnapshotVersion } from '../core/snapshot_version'; import { BatchId } from '../core/types'; -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { debugAssert, hardAssert } from '../util/assert'; import { arrayEquals } from '../util/misc'; diff --git a/packages/firestore/src/model/path.ts b/packages/firestore/src/model/path.ts index edf58d25a5f..8ae933eb355 100644 --- a/packages/firestore/src/model/path.ts +++ b/packages/firestore/src/model/path.ts @@ -190,6 +190,8 @@ abstract class BasePath> { /** * A slash-separated path for navigating resources (documents and collections) * within Firestore. + * + * @internal */ export class ResourcePath extends BasePath { protected construct( @@ -244,7 +246,10 @@ export class ResourcePath extends BasePath { const identifierRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*$/; -/** A dot-separated path for navigating sub-objects within a document. */ +/** + * A dot-separated path for navigating sub-objects within a document. + * @internal + */ export class FieldPath extends BasePath { protected construct( segments: string[], diff --git a/packages/firestore/src/model/server_timestamps.ts b/packages/firestore/src/model/server_timestamps.ts index 532da6a17b5..b2e59c105b9 100644 --- a/packages/firestore/src/model/server_timestamps.ts +++ b/packages/firestore/src/model/server_timestamps.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { Value as ProtoValue, MapValue as ProtoMapValue diff --git a/packages/firestore/src/model/transform_operation.ts b/packages/firestore/src/model/transform_operation.ts index af8c8f23efe..07f6df94366 100644 --- a/packages/firestore/src/model/transform_operation.ts +++ b/packages/firestore/src/model/transform_operation.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { Value as ProtoValue } from '../protos/firestore_proto_api'; import { Serializer, toDouble, toInteger } from '../remote/number_serializer'; import { debugAssert } from '../util/assert'; diff --git a/packages/firestore/src/platform/base64.ts b/packages/firestore/src/platform/base64.ts index b794158d909..877a78c732b 100644 --- a/packages/firestore/src/platform/base64.ts +++ b/packages/firestore/src/platform/base64.ts @@ -29,7 +29,10 @@ export function encodeBase64(raw: string): string { return platform.encodeBase64(raw); } -/** True if and only if the Base64 conversion functions are available. */ +/** + * True if and only if the Base64 conversion functions are available. + * @internal + */ export function isBase64Available(): boolean { return platform.isBase64Available(); } diff --git a/packages/firestore/exp/register.ts b/packages/firestore/src/register.ts similarity index 77% rename from packages/firestore/exp/register.ts rename to packages/firestore/src/register.ts index f6d18e67b98..44f027f8986 100644 --- a/packages/firestore/exp/register.ts +++ b/packages/firestore/src/register.ts @@ -19,30 +19,28 @@ import { _registerComponent, registerVersion, SDK_VERSION -} from '@firebase/app-exp'; +} from '@firebase/app'; import { Component, ComponentType } from '@firebase/component'; import { name, version } from '../package.json'; +import { FirebaseCredentialsProvider } from '../src/api/credentials'; import { setSDKVersion } from '../src/core/version'; -import { Firestore } from '../src/exp/database'; -import { PrivateSettings } from '../src/lite/settings'; -declare module '@firebase/component' { - interface NameServiceMapping { - 'firestore-exp': Firestore; - } -} +import { Firestore } from './api/database'; +import { PrivateSettings } from './lite-api/settings'; export function registerFirestore(variant?: string): void { setSDKVersion(SDK_VERSION); _registerComponent( new Component( - 'firestore-exp', + 'firestore', (container, { options: settings }: { options?: PrivateSettings }) => { - const app = container.getProvider('app-exp').getImmediate()!; + const app = container.getProvider('app').getImmediate()!; const firestoreInstance = new Firestore( app, - container.getProvider('auth-internal') + new FirebaseCredentialsProvider( + container.getProvider('auth-internal') + ) ); settings = { useFetchStreams: true, ...settings }; firestoreInstance._setSettings(settings); diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 7f4b71e8f2b..c43ac066ad7 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -35,7 +35,7 @@ import { Target } from '../core/target'; import { TargetId } from '../core/types'; -import { Timestamp } from '../lite/timestamp'; +import { Timestamp } from '../lite-api/timestamp'; import { TargetData, TargetPurpose } from '../local/target_data'; import { MutableDocument } from '../model/document'; import { DocumentKey } from '../model/document_key'; diff --git a/packages/firestore/src/util/assert.ts b/packages/firestore/src/util/assert.ts index 4a73566583a..6d65e6cd19b 100644 --- a/packages/firestore/src/util/assert.ts +++ b/packages/firestore/src/util/assert.ts @@ -62,6 +62,8 @@ export function hardAssert( * The code of callsites invoking this function are stripped out in production * builds. Any side-effects of code within the debugAssert() invocation will not * happen in this case. + * + * @internal */ export function debugAssert( assertion: boolean, diff --git a/packages/firestore/src/util/byte_stream.ts b/packages/firestore/src/util/byte_stream.ts index 36904956ce1..edb69c460c6 100644 --- a/packages/firestore/src/util/byte_stream.ts +++ b/packages/firestore/src/util/byte_stream.ts @@ -39,8 +39,13 @@ export function toByteStreamReaderHelper( `toByteStreamReader expects positive bytesPerRead, but got ${bytesPerRead}` ); let readFrom = 0; - const reader: ReadableStreamReader = { - async read(): Promise> { + // The TypeScript definition for ReadableStreamReader changed. We use + // `any` here to allow this code to compile with different versions. + // See https://github.com/microsoft/TypeScript/issues/42970 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const reader: any = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async read(): Promise { if (readFrom < source.byteLength) { const result = { value: source.slice(readFrom, readFrom + bytesPerRead), diff --git a/packages/firestore/src/util/byte_string.ts b/packages/firestore/src/util/byte_string.ts index 7ed6fb2b554..f34efb44927 100644 --- a/packages/firestore/src/util/byte_string.ts +++ b/packages/firestore/src/util/byte_string.ts @@ -26,6 +26,7 @@ import { primitiveComparator } from './misc'; * sent on the wire. This class abstracts away this differentiation by holding * the proto byte string in a common class that must be converted into a string * before being sent as a proto. + * @internal */ export class ByteString { static readonly EMPTY_BYTE_STRING = new ByteString(''); diff --git a/packages/firestore/src/util/input_validation.ts b/packages/firestore/src/util/input_validation.ts index 3e81c6d9a9a..47289823f27 100644 --- a/packages/firestore/src/util/input_validation.ts +++ b/packages/firestore/src/util/input_validation.ts @@ -15,8 +15,6 @@ * limitations under the License. */ -import { SetOptions } from '@firebase/firestore-types'; - import { DocumentKey } from '../model/document_key'; import { ResourcePath } from '../model/path'; @@ -46,29 +44,9 @@ export function validateNonEmptyArgument( } } -export function validateSetOptions( - methodName: string, - options: SetOptions | undefined -): SetOptions { - if (options === undefined) { - return { - merge: false - }; - } - - if (options.mergeFields !== undefined && options.merge !== undefined) { - throw new FirestoreError( - Code.INVALID_ARGUMENT, - `Invalid options passed to function ${methodName}(): You cannot ` + - 'specify both "merge" and "mergeFields".' - ); - } - - return options; -} - /** * Validates that two boolean options are not set at the same time. + * @internal */ export function validateIsNotUsedTogether( optionName1: string, @@ -172,6 +150,7 @@ export function tryGetCustomObjectType(input: object): string | null { * * This cast is used in the Lite and Full SDK to verify instance types for * arguments passed to the public API. + * @internal */ export function cast( obj: object, diff --git a/packages/firestore/src/util/log.ts b/packages/firestore/src/util/log.ts index 0a6999dec61..cbdbad38ecf 100644 --- a/packages/firestore/src/util/log.ts +++ b/packages/firestore/src/util/log.ts @@ -60,6 +60,9 @@ export function logError(msg: string, ...obj: unknown[]): void { } } +/** + * @internal + */ export function logWarn(msg: string, ...obj: unknown[]): void { if (logClient.logLevel <= LogLevel.WARN) { const args = obj.map(argToString); diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 35a85f85b9f..be5e529c70a 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -42,10 +42,6 @@ const FieldValue = firebaseExport.FieldValue; const DocumentReference = firebaseExport.DocumentReference; const QueryDocumentSnapshot = firebaseExport.QueryDocumentSnapshot; -const MEMORY_ONLY_BUILD = - typeof process !== 'undefined' && - process.env?.INCLUDE_FIRESTORE_PERSISTENCE === 'false'; - apiDescribe('Database', (persistence: boolean) => { it('can set a document', () => { return withTestDoc(persistence, docRef => { @@ -1093,33 +1089,6 @@ apiDescribe('Database', (persistence: boolean) => { }); }); - // eslint-disable-next-line no-restricted-properties - (MEMORY_ONLY_BUILD ? it : it.skip)( - 'recovers when persistence is missing', - async () => { - await withTestDbs(/* persistence */ false, 2, async dbs => { - for (let i = 0; i < 2; ++i) { - const db = dbs[i]; - - try { - await (i === 0 ? db.enablePersistence() : db.clearPersistence()); - expect.fail( - 'Persistence operation should fail for memory-only build' - ); - } catch (err) { - expect(err.code).to.equal('failed-precondition'); - expect(err.message).to.match( - /You are using the memory-only build of Firestore./ - ); - } - - // Verify client functionality after failed call - await db.doc('coll/doc').get(); - } - }); - } - ); - // eslint-disable-next-line no-restricted-properties (persistence ? it : it.skip)( 'maintains persistence after restarting app', diff --git a/packages/firestore/test/integration/bootstrap.ts b/packages/firestore/test/integration/bootstrap.ts index a38829ab6b9..3b333395fc1 100644 --- a/packages/firestore/test/integration/bootstrap.ts +++ b/packages/firestore/test/integration/bootstrap.ts @@ -15,7 +15,8 @@ * limitations under the License. */ -import '../../index'; +import '../../src/index'; +import '../../compat/index'; /** * This will include all of the test files and compile them as needed diff --git a/packages/firestore/test/integration/util/firebase_export.ts b/packages/firestore/test/integration/util/firebase_export.ts index b6d825c900b..26f77b8b78a 100644 --- a/packages/firestore/test/integration/util/firebase_export.ts +++ b/packages/firestore/test/integration/util/firebase_export.ts @@ -20,22 +20,20 @@ // reference to the minified sources. If you change any exports in this file, // you need to also adjust "integration/firestore/firebase_export.ts". -import firebase from '@firebase/app'; +import firebase from '@firebase/app-compat'; import { FirebaseApp } from '@firebase/app-types'; import * as firestore from '@firebase/firestore-types'; -import { Blob } from '../../../src/api/blob'; +import { Blob } from '../../../compat/api/blob'; import { Firestore, DocumentReference, QueryDocumentSnapshot -} from '../../../src/api/database'; -import { FieldPath } from '../../../src/api/field_path'; -import { FieldValue } from '../../../src/api/field_value'; -import { GeoPoint } from '../../../src/api/geo_point'; -import { Timestamp } from '../../../src/api/timestamp'; -// Import to trigger prototype patching of bundle loading. -import '../../../index.bundle'; +} from '../../../compat/api/database'; +import { FieldPath } from '../../../compat/api/field_path'; +import { FieldValue } from '../../../compat/api/field_value'; +import { GeoPoint } from '../../../compat/api/geo_point'; +import { Timestamp } from '../../../compat/api/timestamp'; // TODO(dimond): Right now we create a new app and Firestore instance for // every test and never clean them up. We may need to revisit. @@ -64,8 +62,9 @@ export function newTestFirestore( nameOrApp ) : nameOrApp; - const firestore = firebase.firestore(app); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const firestore = (firebase as any).firestore(app); if (settings) { firestore.settings(settings); } diff --git a/packages/firestore/test/integration/util/internal_helpers.ts b/packages/firestore/test/integration/util/internal_helpers.ts index 9495fb71c79..5c2962dec1f 100644 --- a/packages/firestore/test/integration/util/internal_helpers.ts +++ b/packages/firestore/test/integration/util/internal_helpers.ts @@ -17,12 +17,12 @@ import * as firestore from '@firebase/firestore-types'; +import { Firestore } from '../../../compat/api/database'; import { CredentialChangeListener, CredentialsProvider, EmptyCredentialsProvider } from '../../../src/api/credentials'; -import { Firestore } from '../../../src/api/database'; import { User } from '../../../src/auth/user'; import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info'; import { newConnection } from '../../../src/platform/connection'; @@ -73,11 +73,8 @@ export class MockCredentialsProvider extends EmptyCredentialsProvider { this.asyncQueue!.enqueueRetryable(async () => this.listener!(newUser)); } - setChangeListener( - asyncQueue: AsyncQueue, - listener: CredentialChangeListener - ): void { - super.setChangeListener(asyncQueue, listener); + start(asyncQueue: AsyncQueue, listener: CredentialChangeListener): void { + super.start(asyncQueue, listener); this.asyncQueue = asyncQueue; this.listener = listener; } diff --git a/packages/firestore/test/lite/helpers.ts b/packages/firestore/test/lite/helpers.ts index 964592f5621..e51c7c606ab 100644 --- a/packages/firestore/test/lite/helpers.ts +++ b/packages/firestore/test/lite/helpers.ts @@ -16,10 +16,10 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -import { initializeApp } from '@firebase/app-exp'; +import { initializeApp } from '@firebase/app'; import { expect } from 'chai'; -import { initializeFirestore, Firestore } from '../../src/lite/database'; +import { initializeFirestore, Firestore } from '../../src/lite-api/database'; import { doc, collection, @@ -28,10 +28,10 @@ import { DocumentReference, SetOptions, PartialWithFieldValue -} from '../../src/lite/reference'; -import { setDoc } from '../../src/lite/reference_impl'; -import { FirestoreSettings } from '../../src/lite/settings'; -import { QueryDocumentSnapshot } from '../../src/lite/snapshot'; +} from '../../src/lite-api/reference'; +import { setDoc } from '../../src/lite-api/reference_impl'; +import { FirestoreSettings } from '../../src/lite-api/settings'; +import { QueryDocumentSnapshot } from '../../src/lite-api/snapshot'; import { AutoId } from '../../src/util/misc'; import { DEFAULT_PROJECT_ID, diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index a0a803d6b13..a5035d58d3b 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -16,26 +16,26 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -import { initializeApp } from '@firebase/app-exp'; +import { initializeApp } from '@firebase/app'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { Bytes } from '../../src/lite/bytes'; +import { Bytes } from '../../src/lite-api/bytes'; import { Firestore, getFirestore, initializeFirestore, terminate -} from '../../src/lite/database'; -import { FieldPath } from '../../src/lite/field_path'; -import { FieldValue } from '../../src/lite/field_value'; +} from '../../src/lite-api/database'; +import { FieldPath } from '../../src/lite-api/field_path'; +import { FieldValue } from '../../src/lite-api/field_value'; import { arrayRemove, arrayUnion, deleteField, increment, serverTimestamp -} from '../../src/lite/field_value_impl'; +} from '../../src/lite-api/field_value_impl'; import { endAt, endBefore, @@ -46,7 +46,7 @@ import { startAfter, startAt, where -} from '../../src/lite/query'; +} from '../../src/lite-api/query'; import { collection, CollectionReference, @@ -60,7 +60,7 @@ import { WithFieldValue, PartialWithFieldValue, UpdateData -} from '../../src/lite/reference'; +} from '../../src/lite-api/reference'; import { addDoc, deleteDoc, @@ -68,15 +68,15 @@ import { getDocs, setDoc, updateDoc -} from '../../src/lite/reference_impl'; +} from '../../src/lite-api/reference_impl'; import { snapshotEqual, QuerySnapshot, QueryDocumentSnapshot -} from '../../src/lite/snapshot'; -import { Timestamp } from '../../src/lite/timestamp'; -import { runTransaction } from '../../src/lite/transaction'; -import { writeBatch } from '../../src/lite/write_batch'; +} from '../../src/lite-api/snapshot'; +import { Timestamp } from '../../src/lite-api/timestamp'; +import { runTransaction } from '../../src/lite-api/transaction'; +import { writeBatch } from '../../src/lite-api/write_batch'; import { DEFAULT_PROJECT_ID, DEFAULT_SETTINGS diff --git a/packages/firestore/test/register.ts b/packages/firestore/test/register.ts new file mode 100644 index 00000000000..be93ecdccc2 --- /dev/null +++ b/packages/firestore/test/register.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase from '@firebase/app-compat'; +import { FirebaseNamespace } from '@firebase/app-types'; + +import { registerFirestore as registerFirestoreCompat } from '../compat'; +import { registerFirestore } from '../src/register'; + +registerFirestore(); +registerFirestoreCompat(firebase as unknown as FirebaseNamespace); diff --git a/packages/firestore/test/unit/api/blob.test.ts b/packages/firestore/test/unit/api/blob.test.ts index 164eea702c4..ae85ebde898 100644 --- a/packages/firestore/test/unit/api/blob.test.ts +++ b/packages/firestore/test/unit/api/blob.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -import { Blob } from '../../../src/api/blob'; +import { Blob } from '../../../compat/api/blob'; import { blob, expectEqual, expectNotEqual } from '../../util/helpers'; describe('Blob', () => { diff --git a/packages/firestore/test/unit/api/document_change.test.ts b/packages/firestore/test/unit/api/document_change.test.ts index 0d167efe07d..15445d2d01d 100644 --- a/packages/firestore/test/unit/api/document_change.test.ts +++ b/packages/firestore/test/unit/api/document_change.test.ts @@ -17,11 +17,11 @@ import { expect } from 'chai'; +import { Query } from '../../../src/api/reference'; +import { ExpUserDataWriter } from '../../../src/api/reference_impl'; +import { QuerySnapshot } from '../../../src/api/snapshot'; import { Query as InternalQuery } from '../../../src/core/query'; import { View } from '../../../src/core/view'; -import { Query } from '../../../src/exp/reference'; -import { ExpUserDataWriter } from '../../../src/exp/reference_impl'; -import { QuerySnapshot } from '../../../src/exp/snapshot'; import { documentKeySet } from '../../../src/model/collections'; import { MutableDocument } from '../../../src/model/document'; import { DocumentKey } from '../../../src/model/document_key'; diff --git a/packages/firestore/test/unit/api/field_value.test.ts b/packages/firestore/test/unit/api/field_value.test.ts index 5c437446633..9ba1b0585a1 100644 --- a/packages/firestore/test/unit/api/field_value.test.ts +++ b/packages/firestore/test/unit/api/field_value.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -import { FieldValue } from '../../../src/api/field_value'; +import { FieldValue } from '../../../compat/api/field_value'; import { expectEqual, expectNotEqual } from '../../util/helpers'; describe('FieldValue', () => { diff --git a/packages/firestore/test/unit/core/query.test.ts b/packages/firestore/test/unit/core/query.test.ts index 0c9a30885e0..248db631b3d 100644 --- a/packages/firestore/test/unit/core/query.test.ts +++ b/packages/firestore/test/unit/core/query.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -import { Blob } from '../../../src/api/blob'; +import { Blob } from '../../../compat/api/blob'; import { GeoPoint } from '../../../src/api/geo_point'; import { Timestamp } from '../../../src/api/timestamp'; import { diff --git a/packages/firestore/test/unit/local/local_store.test.ts b/packages/firestore/test/unit/local/local_store.test.ts index 9685fa71d20..71e0ab862cd 100644 --- a/packages/firestore/test/unit/local/local_store.test.ts +++ b/packages/firestore/test/unit/local/local_store.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -import { FieldValue } from '../../../src/api/field_value'; +import { FieldValue } from '../../../compat/api/field_value'; import { Timestamp } from '../../../src/api/timestamp'; import { User } from '../../../src/auth/user'; import { BundledDocuments, NamedQuery } from '../../../src/core/bundle'; diff --git a/packages/firestore/test/unit/model/mutation.test.ts b/packages/firestore/test/unit/model/mutation.test.ts index d4b4d718be1..03bb4aa706a 100644 --- a/packages/firestore/test/unit/model/mutation.test.ts +++ b/packages/firestore/test/unit/model/mutation.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -import { FieldValue } from '../../../src/api/field_value'; +import { FieldValue } from '../../../compat/api/field_value'; import { Timestamp } from '../../../src/api/timestamp'; import { MutableDocument } from '../../../src/model/document'; import { diff --git a/packages/firestore/test/unit/remote/serializer.helper.ts b/packages/firestore/test/unit/remote/serializer.helper.ts index 43458d2aae2..bee01fbdaa6 100644 --- a/packages/firestore/test/unit/remote/serializer.helper.ts +++ b/packages/firestore/test/unit/remote/serializer.helper.ts @@ -17,9 +17,12 @@ import { expect } from 'chai'; -import { Blob } from '../../../src/api/blob'; -import { DocumentReference, UserDataWriter } from '../../../src/api/database'; -import { FieldValue } from '../../../src/api/field_value'; +import { Blob } from '../../../compat/api/blob'; +import { + DocumentReference, + UserDataWriter +} from '../../../compat/api/database'; +import { FieldValue } from '../../../compat/api/field_value'; import { GeoPoint } from '../../../src/api/geo_point'; import { Timestamp } from '../../../src/api/timestamp'; import { DatabaseId } from '../../../src/core/database_info'; @@ -46,7 +49,7 @@ import { targetEquals, TargetImpl } from '../../../src/core/target'; -import { parseQueryValue } from '../../../src/lite/user_data_reader'; +import { parseQueryValue } from '../../../src/lite-api/user_data_reader'; import { TargetData, TargetPurpose } from '../../../src/local/target_data'; import { FieldMask } from '../../../src/model/field_mask'; import { diff --git a/packages/firestore/test/unit/specs/spec_builder.ts b/packages/firestore/test/unit/specs/spec_builder.ts index 4e4b9b268b5..1f36c1a9cf7 100644 --- a/packages/firestore/test/unit/specs/spec_builder.ts +++ b/packages/firestore/test/unit/specs/spec_builder.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { UserDataWriter } from '../../../src/api/database'; +import { UserDataWriter } from '../../../compat/api/database'; import { hasLimitToFirst, hasLimitToLast, diff --git a/packages/firestore/test/unit/specs/spec_test_runner.ts b/packages/firestore/test/unit/specs/spec_test_runner.ts index 785ca80de30..3ba157871dc 100644 --- a/packages/firestore/test/unit/specs/spec_test_runner.ts +++ b/packages/firestore/test/unit/specs/spec_test_runner.ts @@ -17,6 +17,7 @@ import { expect } from 'chai'; +import { LoadBundleTask } from '../../../src/api/bundle'; import { EmptyCredentialsProvider } from '../../../src/api/credentials'; import { User } from '../../../src/auth/user'; import { ComponentConfiguration } from '../../../src/core/component_provider'; @@ -57,7 +58,6 @@ import { ChangeType, DocumentViewChange } from '../../../src/core/view_snapshot'; -import { LoadBundleTask } from '../../../src/exp/bundle'; import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence'; import { DbPrimaryClient, diff --git a/packages/firestore/test/util/api_helpers.ts b/packages/firestore/test/util/api_helpers.ts index 35f89e11c85..432f3fe4666 100644 --- a/packages/firestore/test/util/api_helpers.ts +++ b/packages/firestore/test/util/api_helpers.ts @@ -18,8 +18,6 @@ // Helpers here mock Firestore in order to unit-test API types. Do NOT use // these in any integration test, where we expect working Firestore object. -import { Provider, ComponentContainer } from '@firebase/component'; - import { CollectionReference, DocumentReference, @@ -29,28 +27,29 @@ import { Query, QuerySnapshot, UserDataWriter -} from '../../src/api/database'; -import { DatabaseId } from '../../src/core/database_info'; -import { newQueryForPath, Query as InternalQuery } from '../../src/core/query'; -import { - ChangeType, - DocumentViewChange, - ViewSnapshot -} from '../../src/core/view_snapshot'; +} from '../../compat/api/database'; +import { EmptyCredentialsProvider } from '../../src/api/credentials'; import { ensureFirestoreConfigured, Firestore as ExpFirestore -} from '../../src/exp/database'; +} from '../../src/api/database'; import { Query as ExpQuery, CollectionReference as ExpCollectionReference -} from '../../src/exp/reference'; -import { ExpUserDataWriter } from '../../src/exp/reference_impl'; +} from '../../src/api/reference'; +import { ExpUserDataWriter } from '../../src/api/reference_impl'; import { QuerySnapshot as ExpQuerySnapshot, DocumentSnapshot as ExpDocumentSnapshot, SnapshotMetadata -} from '../../src/exp/snapshot'; +} from '../../src/api/snapshot'; +import { DatabaseId } from '../../src/core/database_info'; +import { newQueryForPath, Query as InternalQuery } from '../../src/core/query'; +import { + ChangeType, + DocumentViewChange, + ViewSnapshot +} from '../../src/core/view_snapshot'; import { DocumentKeySet } from '../../src/model/collections'; import { DocumentSet } from '../../src/model/document_set'; import { JsonObject } from '../../src/model/object_value'; @@ -70,10 +69,7 @@ export function firestore(): Firestore { export function newTestFirestore(projectId = 'new-project'): Firestore { return new Firestore( new DatabaseId(projectId), - new ExpFirestore( - new DatabaseId(projectId), - new Provider('auth-internal', new ComponentContainer('default')) - ), + new ExpFirestore(new DatabaseId(projectId), new EmptyCredentialsProvider()), new IndexedDbPersistenceProvider() ); } diff --git a/packages/firestore/test/util/helpers.ts b/packages/firestore/test/util/helpers.ts index 5d1cb3caf99..a1ab260288e 100644 --- a/packages/firestore/test/util/helpers.ts +++ b/packages/firestore/test/util/helpers.ts @@ -18,8 +18,8 @@ import * as firestore from '@firebase/firestore-types'; import { expect } from 'chai'; -import { Blob } from '../../src/api/blob'; -import { DocumentReference } from '../../src/api/database'; +import { Blob } from '../../compat/api/blob'; +import { DocumentReference } from '../../compat/api/database'; import { Timestamp } from '../../src/api/timestamp'; import { BundledDocuments } from '../../src/core/bundle'; import { DatabaseId } from '../../src/core/database_info'; @@ -53,7 +53,7 @@ import { parseSetData, parseUpdateData, UserDataReader -} from '../../src/lite/user_data_reader'; +} from '../../src/lite-api/user_data_reader'; import { LocalViewChanges } from '../../src/local/local_view_changes'; import { TargetData, TargetPurpose } from '../../src/local/target_data'; import { diff --git a/packages-exp/functions-compat/.eslintrc.js b/packages/functions-compat/.eslintrc.js similarity index 100% rename from packages-exp/functions-compat/.eslintrc.js rename to packages/functions-compat/.eslintrc.js diff --git a/packages-exp/functions-compat/karma.conf.js b/packages/functions-compat/karma.conf.js similarity index 100% rename from packages-exp/functions-compat/karma.conf.js rename to packages/functions-compat/karma.conf.js diff --git a/packages-exp/functions-compat/package.json b/packages/functions-compat/package.json similarity index 85% rename from packages-exp/functions-compat/package.json rename to packages/functions-compat/package.json index 4cca81422d3..846931a6ea9 100644 --- a/packages-exp/functions-compat/package.json +++ b/packages/functions-compat/package.json @@ -2,7 +2,6 @@ "name": "@firebase/functions-compat", "version": "0.0.900", "description": "", - "private": true, "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.node.cjs.js", "browser": "dist/index.esm2017.js", @@ -22,7 +21,7 @@ "typescript": "4.2.2" }, "repository": { - "directory": "packages-exp/functions-compat", + "directory": "packages/functions-compat", "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, @@ -43,14 +42,13 @@ "test:browser:debug": "karma start --browsers=Chrome --auto-watch", "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", "test:emulator": "env FIREBASE_FUNCTIONS_HOST=http://localhost FIREBASE_FUNCTIONS_PORT=5005 run-p test:node", - "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../functions-exp/dist/functions-exp-public.d.ts -o dist/src/index.d.ts -a -r Functions:types.FirebaseFunctions -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/functions" + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../functions/dist/functions-public.d.ts -o dist/src/index.d.ts -a -r Functions:types.FirebaseFunctions -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/functions" }, "typings": "dist/src/index.d.ts", "dependencies": { "@firebase/component": "0.5.6", - "@firebase/functions-exp": "0.0.900", + "@firebase/functions": "0.6.15", "@firebase/functions-types": "0.4.0", - "@firebase/messaging-types": "0.5.0", "@firebase/util": "1.3.0", "tslib": "^2.1.0" }, diff --git a/packages/functions-compat/rollup.config.js b/packages/functions-compat/rollup.config.js new file mode 100644 index 00000000000..36b263979d5 --- /dev/null +++ b/packages/functions-compat/rollup.config.js @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import pkg from './package.json'; + +const deps = Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)); + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; +const es5Builds = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + /** + * Node.js Build + */ + { + input: 'src/index.node.ts', + output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; +const es2017Builds = [ + { + /** + * Browser Build + */ + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +export default [ + ...es5Builds, + ...es2017Builds +]; diff --git a/packages-exp/functions-compat/src/callable.test.ts b/packages/functions-compat/src/callable.test.ts similarity index 96% rename from packages-exp/functions-compat/src/callable.test.ts rename to packages/functions-compat/src/callable.test.ts index 248cae71c3e..bd7f70b9e98 100644 --- a/packages-exp/functions-compat/src/callable.test.ts +++ b/packages/functions-compat/src/callable.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ import { expect } from 'chai'; -import { FunctionsErrorCode } from '@firebase/functions-exp'; +import { FunctionsErrorCode } from '@firebase/functions'; import { createTestService } from '../test/utils'; import { firebase, FirebaseApp } from '@firebase/app-compat'; @@ -35,8 +35,8 @@ async function expectError( await promise; } catch (e) { failed = true; - // Errors coming from callable functions usually have the functions-exp - // code in the message since it's thrown inside functions-exp. + // Errors coming from callable functions usually have the functions + // code in the message since it's thrown inside functions. expect(e.code).to.match(new RegExp(`functions.*/${code}`)); expect(e.message).to.equal(message); expect(e.details).to.deep.equal(details); diff --git a/packages-exp/functions-compat/src/index.node.ts b/packages/functions-compat/src/index.node.ts similarity index 100% rename from packages-exp/functions-compat/src/index.node.ts rename to packages/functions-compat/src/index.node.ts diff --git a/packages-exp/functions-compat/src/index.ts b/packages/functions-compat/src/index.ts similarity index 100% rename from packages-exp/functions-compat/src/index.ts rename to packages/functions-compat/src/index.ts diff --git a/packages-exp/functions-compat/src/register.ts b/packages/functions-compat/src/register.ts similarity index 82% rename from packages-exp/functions-compat/src/register.ts rename to packages/functions-compat/src/register.ts index 70b3ab63a3c..6a83f8fd265 100644 --- a/packages-exp/functions-compat/src/register.ts +++ b/packages/functions-compat/src/register.ts @@ -16,8 +16,7 @@ */ import firebase, { - _FirebaseNamespace, - FirebaseApp + _FirebaseNamespace } from '@firebase/app-compat'; import { FunctionsService } from './service'; import { @@ -27,18 +26,9 @@ import { ComponentContainer, InstanceFactoryOptions } from '@firebase/component'; -import { Functions as FunctionsServiceExp } from '@firebase/functions-exp'; const DEFAULT_REGION = 'us-central1'; -declare module '@firebase/component' { - interface NameServiceMapping { - 'app-compat': FirebaseApp; - 'functions-compat': FunctionsService; - 'functions-exp': FunctionsServiceExp; - } -} - const factory: InstanceFactory<'functions-compat'> = ( container: ComponentContainer, { instanceIdentifier: regionOrCustomDomain }: InstanceFactoryOptions @@ -46,7 +36,7 @@ const factory: InstanceFactory<'functions-compat'> = ( // Dependencies const app = container.getProvider('app-compat').getImmediate(); const functionsServiceExp = container - .getProvider('functions-exp') + .getProvider('functions') .getImmediate({ identifier: regionOrCustomDomain ?? DEFAULT_REGION }); diff --git a/packages-exp/functions-compat/src/service.test.ts b/packages/functions-compat/src/service.test.ts similarity index 98% rename from packages-exp/functions-compat/src/service.test.ts rename to packages/functions-compat/src/service.test.ts index 04c26d6950a..4ff312dd246 100644 --- a/packages-exp/functions-compat/src/service.test.ts +++ b/packages/functions-compat/src/service.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import { createTestService } from '../test/utils'; import { FunctionsService } from './service'; import { firebase, FirebaseApp } from '@firebase/app-compat'; -import * as functionsExp from '@firebase/functions-exp'; +import * as functionsExp from '@firebase/functions'; import { stub, match, SinonStub } from 'sinon'; import * as sinonChai from 'sinon-chai'; diff --git a/packages-exp/functions-compat/src/service.ts b/packages/functions-compat/src/service.ts similarity index 98% rename from packages-exp/functions-compat/src/service.ts rename to packages/functions-compat/src/service.ts index 5921dd60e84..b7a5a7d67dc 100644 --- a/packages-exp/functions-compat/src/service.ts +++ b/packages/functions-compat/src/service.ts @@ -21,7 +21,7 @@ import { connectFunctionsEmulator as useFunctionsEmulatorExp, HttpsCallableOptions, Functions as FunctionsServiceExp -} from '@firebase/functions-exp'; +} from '@firebase/functions'; import { FirebaseApp, _FirebaseService } from '@firebase/app-compat'; import { FirebaseError } from '@firebase/util'; diff --git a/packages-exp/functions-compat/test/utils.ts b/packages/functions-compat/test/utils.ts similarity index 95% rename from packages-exp/functions-compat/test/utils.ts rename to packages/functions-compat/test/utils.ts index 8d4bac907c8..212f5204853 100644 --- a/packages-exp/functions-compat/test/utils.ts +++ b/packages/functions-compat/test/utils.ts @@ -17,7 +17,7 @@ import { FirebaseApp } from '@firebase/app-compat'; import { FunctionsService } from '../src/service'; -import { getFunctions } from '@firebase/functions-exp'; +import { getFunctions } from '@firebase/functions'; export function createTestService( app: FirebaseApp, diff --git a/packages-exp/firebase-exp/tsconfig.json b/packages/functions-compat/tsconfig.json similarity index 100% rename from packages-exp/firebase-exp/tsconfig.json rename to packages/functions-compat/tsconfig.json diff --git a/packages/functions-types/index.d.ts b/packages/functions-types/index.d.ts index 073821f8d52..4d1770377d3 100644 --- a/packages/functions-types/index.d.ts +++ b/packages/functions-types/index.d.ts @@ -147,6 +147,6 @@ export interface HttpsError extends Error { declare module '@firebase/component' { interface NameServiceMapping { - 'functions': FirebaseFunctions; + 'functions-compat': FirebaseFunctions; } } diff --git a/packages/functions/.eslintrc.js b/packages/functions/.eslintrc.js index 16276950320..11fa60d3e6a 100644 --- a/packages/functions/.eslintrc.js +++ b/packages/functions/.eslintrc.js @@ -1,3 +1,22 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const path = require('path'); + module.exports = { extends: '../../config/.eslintrc.js', parserOptions: { @@ -5,5 +24,14 @@ module.exports = { // to make vscode-eslint work with monorepo // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 tsconfigRootDir: __dirname + }, + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + 'packageDir': [path.resolve(__dirname, '../../'), __dirname], + devDependencies: true + } + ] } }; diff --git a/packages/functions/.npmignore b/packages/functions/.npmignore deleted file mode 100644 index 682c8f74a52..00000000000 --- a/packages/functions/.npmignore +++ /dev/null @@ -1,9 +0,0 @@ -# Directories not needed by end users -/src -test - -# Files not needed by end users -gulpfile.js -index.ts -karma.conf.js -tsconfig.json \ No newline at end of file diff --git a/packages-exp/functions-exp/api-extractor.json b/packages/functions/api-extractor.json similarity index 100% rename from packages-exp/functions-exp/api-extractor.json rename to packages/functions/api-extractor.json diff --git a/packages/functions/index.node.ts b/packages/functions/index.node.ts deleted file mode 100644 index dab160f9bfe..00000000000 --- a/packages/functions/index.node.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import firebase from '@firebase/app'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { registerFunctions } from './src/config'; -import nodeFetch from 'node-fetch'; - -import { name, version } from './package.json'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -registerFunctions(firebase as _FirebaseNamespace, nodeFetch as any); -firebase.registerVersion(name, version, 'node'); diff --git a/packages/functions/index.ts b/packages/functions/index.ts deleted file mode 100644 index 24dbbdca2b2..00000000000 --- a/packages/functions/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import firebase from '@firebase/app'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import * as types from '@firebase/functions-types'; -import { registerFunctions } from './src/config'; - -import { name, version } from './package.json'; - -registerFunctions(firebase as _FirebaseNamespace, fetch.bind(self)); -firebase.registerVersion(name, version); - -declare module '@firebase/app-types' { - interface FirebaseNamespace { - functions?: { - (app?: FirebaseApp): types.FirebaseFunctions; - Functions: typeof types.FirebaseFunctions; - }; - } - interface FirebaseApp { - functions?(regionOrCustomDomain?: string): types.FirebaseFunctions; - } -} diff --git a/packages/functions/karma.conf.js b/packages/functions/karma.conf.js index 3d42695ced9..d180371aeba 100644 --- a/packages/functions/karma.conf.js +++ b/packages/functions/karma.conf.js @@ -17,7 +17,7 @@ const karmaBase = require('../../config/karma.base'); -const files = [`test/**/*`]; +const files = [`src/**/*.test.ts`]; module.exports = function (config) { const karmaConfig = Object.assign({}, karmaBase, { diff --git a/packages/functions/package.json b/packages/functions/package.json index 33a1809a994..9b00fb4ce75 100644 --- a/packages/functions/package.json +++ b/packages/functions/package.json @@ -4,35 +4,38 @@ "description": "", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.node.cjs.js", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", "files": [ "dist" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c", + "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/functions --include-dependencies build", + "build:release": "rollup -c rollup.config.release.js && yarn api-report", "dev": "rollup -c -w", "test": "run-p lint test:all", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", "test:all": "run-p test:browser test:node", "test:browser": "karma start --single-run", "test:browser:debug": "karma start --browsers=Chrome --auto-watch", - "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file index.node.ts --config ../../config/mocharc.node.js", - "test:emulator": "env FIREBASE_FUNCTIONS_EMULATOR_ORIGIN=http://localhost:5005 run-p test:node" + "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", + "test:emulator": "env FIREBASE_FUNCTIONS_EMULATOR_ORIGIN=http://localhost:5005 run-p test:node", + "api-report": "api-extractor run --local --verbose", + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/functions-public.d.ts" }, "license": "Apache-2.0", "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" }, "devDependencies": { "@firebase/app": "0.6.30", - "@firebase/messaging": "0.8.0", "rollup": "2.52.2", + "@rollup/plugin-json": "4.1.0", "rollup-plugin-typescript2": "0.30.0", "typescript": "4.2.2" }, @@ -44,11 +47,13 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/index.d.ts", + "typings": "dist/src/index.d.ts", "dependencies": { "@firebase/component": "0.5.6", - "@firebase/functions-types": "0.4.0", - "@firebase/messaging-types": "0.5.0", + "@firebase/messaging-interop-types": "0.0.1", + "@firebase/auth-interop-types": "0.1.6", + "@firebase/app-check-interop-types": "0.1.0", + "@firebase/util": "1.3.0", "node-fetch": "2.6.1", "tslib": "^2.1.0" }, @@ -57,5 +62,6 @@ ".ts" ], "reportDir": "./coverage/node" - } -} + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages/functions/rollup.config.js b/packages/functions/rollup.config.js index 700cea6c025..be5093746ea 100644 --- a/packages/functions/rollup.config.js +++ b/packages/functions/rollup.config.js @@ -20,10 +20,7 @@ import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; import pkg from './package.json'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); - +const deps = Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)); /** * ES5 Builds */ @@ -39,22 +36,25 @@ const es5Builds = [ * Browser Builds */ { - input: 'index.ts', - output: [{ file: pkg.module, format: 'es', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + input: 'src/index.ts', + output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins }, /** * Node.js Build */ { - input: 'index.node.ts', + input: 'src/index.node.ts', output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins } ]; +/** + * ES2017 Builds + */ const es2017BuildPlugins = [ typescriptPlugin({ typescript, @@ -67,22 +67,19 @@ const es2017BuildPlugins = [ json({ preferConst: true }) ]; -/** - * ES2017 Builds - */ const es2017Builds = [ { /** * Browser Build */ - input: 'index.ts', + input: 'src/index.ts', output: { - file: pkg.esm2017, + file: pkg.browser, format: 'es', sourcemap: true }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins } ]; diff --git a/packages-exp/functions-exp/src/api.ts b/packages/functions/src/api.ts similarity index 97% rename from packages-exp/functions-exp/src/api.ts rename to packages/functions/src/api.ts index a1f91b48a7f..008d541b059 100644 --- a/packages-exp/functions-exp/src/api.ts +++ b/packages/functions/src/api.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; +import { _getProvider, FirebaseApp, getApp } from '@firebase/app'; import { FUNCTIONS_TYPE } from './constants'; import { Provider } from '@firebase/component'; @@ -43,7 +43,7 @@ export function getFunctions( regionOrCustomDomain: string = DEFAULT_REGION ): Functions { // Dependencies - const functionsProvider: Provider<'functions-exp'> = _getProvider( + const functionsProvider: Provider<'functions'> = _getProvider( getModularInstance(app), FUNCTIONS_TYPE ); diff --git a/packages/functions/src/api/error.ts b/packages/functions/src/api/error.ts deleted file mode 100644 index 72361b8f721..00000000000 --- a/packages/functions/src/api/error.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { HttpsError, FunctionsErrorCode } from '@firebase/functions-types'; -import { Serializer } from '../serializer'; -import { HttpResponseBody } from './service'; - -/** - * Standard error codes for different ways a request can fail, as defined by: - * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto - * - * This map is used primarily to convert from a backend error code string to - * a client SDK error code string, and make sure it's in the supported set. - */ -const errorCodeMap: { [name: string]: FunctionsErrorCode } = { - OK: 'ok', - CANCELLED: 'cancelled', - UNKNOWN: 'unknown', - INVALID_ARGUMENT: 'invalid-argument', - DEADLINE_EXCEEDED: 'deadline-exceeded', - NOT_FOUND: 'not-found', - ALREADY_EXISTS: 'already-exists', - PERMISSION_DENIED: 'permission-denied', - UNAUTHENTICATED: 'unauthenticated', - RESOURCE_EXHAUSTED: 'resource-exhausted', - FAILED_PRECONDITION: 'failed-precondition', - ABORTED: 'aborted', - OUT_OF_RANGE: 'out-of-range', - UNIMPLEMENTED: 'unimplemented', - INTERNAL: 'internal', - UNAVAILABLE: 'unavailable', - DATA_LOSS: 'data-loss' -}; - -/** - * An explicit error that can be thrown from a handler to send an error to the - * client that called the function. - */ -export class HttpsErrorImpl extends Error implements HttpsError { - /** - * A standard error code that will be returned to the client. This also - * determines the HTTP status code of the response, as defined in code.proto. - */ - readonly code: FunctionsErrorCode; - - /** - * Extra data to be converted to JSON and included in the error response. - */ - readonly details?: unknown; - - constructor(code: FunctionsErrorCode, message?: string, details?: unknown) { - super(message); - - // This is a workaround for a bug in TypeScript when extending Error: - // tslint:disable-next-line - // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, HttpsErrorImpl.prototype); - - this.code = code; - this.details = details; - } -} - -/** - * Takes an HTTP status code and returns the corresponding ErrorCode. - * This is the standard HTTP status code -> error mapping defined in: - * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto - * - * @param status An HTTP status code. - * @return The corresponding ErrorCode, or ErrorCode.UNKNOWN if none. - */ -function codeForHTTPStatus(status: number): FunctionsErrorCode { - // Make sure any successful status is OK. - if (status >= 200 && status < 300) { - return 'ok'; - } - switch (status) { - case 0: - // This can happen if the server returns 500. - return 'internal'; - case 400: - return 'invalid-argument'; - case 401: - return 'unauthenticated'; - case 403: - return 'permission-denied'; - case 404: - return 'not-found'; - case 409: - return 'aborted'; - case 429: - return 'resource-exhausted'; - case 499: - return 'cancelled'; - case 500: - return 'internal'; - case 501: - return 'unimplemented'; - case 503: - return 'unavailable'; - case 504: - return 'deadline-exceeded'; - default: // ignore - } - return 'unknown'; -} - -/** - * Takes an HTTP response and returns the corresponding Error, if any. - */ -export function _errorForResponse( - status: number, - bodyJSON: HttpResponseBody | null, - serializer: Serializer -): Error | null { - let code = codeForHTTPStatus(status); - - // Start with reasonable defaults from the status code. - let description: string = code; - - let details: unknown = undefined; - - // Then look through the body for explicit details. - try { - const errorJSON = bodyJSON && bodyJSON.error; - if (errorJSON) { - const status = errorJSON.status; - if (typeof status === 'string') { - if (!errorCodeMap[status]) { - // They must've included an unknown error code in the body. - return new HttpsErrorImpl('internal', 'internal'); - } - code = errorCodeMap[status]; - - // TODO(klimt): Add better default descriptions for error enums. - // The default description needs to be updated for the new code. - description = status; - } - - const message = errorJSON.message; - if (typeof message === 'string') { - description = message; - } - - details = errorJSON.details; - if (details !== undefined) { - details = serializer.decode(details); - } - } - } catch (e) { - // If we couldn't parse explicit error data, that's fine. - } - - if (code === 'ok') { - // Technically, there's an edge case where a developer could explicitly - // return an error code of OK, and we will treat it as success, but that - // seems reasonable. - return null; - } - - return new HttpsErrorImpl(code, description, details); -} diff --git a/packages/functions/src/api/service.ts b/packages/functions/src/api/service.ts deleted file mode 100644 index 6ab18741bac..00000000000 --- a/packages/functions/src/api/service.ts +++ /dev/null @@ -1,338 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseApp } from '@firebase/app-types'; -import { FirebaseService } from '@firebase/app-types/private'; -import { - FirebaseFunctions, - HttpsCallable, - HttpsCallableResult, - HttpsCallableOptions -} from '@firebase/functions-types'; -import { _errorForResponse, HttpsErrorImpl } from './error'; -import { ContextProvider } from '../context'; -import { Serializer } from '../serializer'; -import { Provider } from '@firebase/component'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { FirebaseMessagingName } from '@firebase/messaging-types'; -import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; - -/** - * The response to an http request. - */ -interface HttpResponse { - status: number; - json: HttpResponseBody | null; -} -/** - * Describes the shape of the HttpResponse body. - * It makes functions that would otherwise take {} able to access the - * possible elements in the body more easily - */ -export interface HttpResponseBody { - data?: unknown; - result?: unknown; - error?: { - message?: unknown; - status?: unknown; - details?: unknown; - }; -} - -/** - * Returns a Promise that will be rejected after the given duration. - * The error will be of type HttpsErrorImpl. - * - * @param millis Number of milliseconds to wait before rejecting. - */ -function failAfter( - millis: number -): { - timer: number | NodeJS.Timeout; - promise: Promise; -} { - let timer!: number | NodeJS.Timeout; - const promise = new Promise((_, reject) => { - timer = setTimeout(() => { - reject(new HttpsErrorImpl('deadline-exceeded', 'deadline-exceeded')); - }, millis); - }); - - return { - timer, - promise - }; -} - -/** - * The main class for the Firebase Functions SDK. - */ -export class Service implements FirebaseFunctions, FirebaseService { - private readonly contextProvider: ContextProvider; - private readonly serializer = new Serializer(); - private emulatorOrigin: string | null = null; - private cancelAllRequests: Promise; - private deleteService!: () => void; - private region: string; - private customDomain: string | null; - - /** - * Creates a new Functions service for the given app and (optional) region or custom domain. - * @param app_ The FirebaseApp to use. - * @param regionOrCustomDomain_ one of: - * a) A region to call functions from, such as us-central1 - * b) A custom domain to use as a functions prefix, such as https://mydomain.com - */ - constructor( - private app_: FirebaseApp, - authProvider: Provider, - messagingProvider: Provider, - private appCheckProvider: Provider, - regionOrCustomDomain_: string = 'us-central1', - readonly fetchImpl: typeof fetch - ) { - this.contextProvider = new ContextProvider(authProvider, messagingProvider); - // Cancels all ongoing requests when resolved. - this.cancelAllRequests = new Promise(resolve => { - this.deleteService = () => { - return resolve(); - }; - }); - - // Resolve the region or custom domain overload by attempting to parse it. - try { - const url = new URL(regionOrCustomDomain_); - this.customDomain = url.origin; - this.region = 'us-central1'; - } catch (e) { - this.customDomain = null; - this.region = regionOrCustomDomain_; - } - } - - get app(): FirebaseApp { - return this.app_; - } - - INTERNAL = { - delete: (): Promise => { - return Promise.resolve(this.deleteService()); - } - }; - - /** - * Returns the URL for a callable with the given name. - * @param name The name of the callable. - */ - _url(name: string): string { - const projectId = this.app_.options.projectId; - if (this.emulatorOrigin !== null) { - const origin = this.emulatorOrigin; - return `${origin}/${projectId}/${this.region}/${name}`; - } - - if (this.customDomain !== null) { - return `${this.customDomain}/${name}`; - } - - return `https://${this.region}-${projectId}.cloudfunctions.net/${name}`; - } - - /** - * Modify this instance to communicate with the Cloud Functions emulator. - * - * Note: this must be called before this instance has been used to do any operations. - * - * @param host The emulator host (ex: localhost) - * @param port The emulator port (ex: 5001) - */ - useEmulator(host: string, port: number): void { - this.emulatorOrigin = `http://${host}:${port}`; - } - - /** - * Changes this instance to point to a Cloud Functions emulator running - * locally. See https://firebase.google.com/docs/functions/local-emulator - * - * @deprecated Prefer the useEmulator(host, port) method. - * @param origin The origin of the local emulator, such as - * "http://localhost:5005". - */ - useFunctionsEmulator(origin: string): void { - this.emulatorOrigin = origin; - } - - /** - * Returns a reference to the callable https trigger with the given name. - * @param name The name of the trigger. - */ - httpsCallable(name: string, options?: HttpsCallableOptions): HttpsCallable { - return data => { - return this.call(name, data, options || {}); - }; - } - - /** - * Does an HTTP POST and returns the completed response. - * @param url The url to post to. - * @param body The JSON body of the post. - * @param headers The HTTP headers to include in the request. - * @return A Promise that will succeed when the request finishes. - */ - private async postJSON( - url: string, - body: {}, - headers: { [key: string]: string } - ): Promise { - headers['Content-Type'] = 'application/json'; - - const appCheckToken = await this.getAppCheckToken(); - if (appCheckToken !== null) { - headers['X-Firebase-AppCheck'] = appCheckToken; - } - - let response: Response; - try { - response = await this.fetchImpl(url, { - method: 'POST', - body: JSON.stringify(body), - headers - }); - } catch (e) { - // This could be an unhandled error on the backend, or it could be a - // network error. There's no way to know, since an unhandled error on the - // backend will fail to set the proper CORS header, and thus will be - // treated as a network error by fetch. - return { - status: 0, - json: null - }; - } - let json: HttpResponseBody | null = null; - try { - json = await response.json(); - } catch (e) { - // If we fail to parse JSON, it will fail the same as an empty body. - } - return { - status: response.status, - json - }; - } - - private async getAppCheckToken(): Promise { - const appCheck = this.appCheckProvider.getImmediate({ optional: true }); - if (appCheck) { - const result = await appCheck.getToken(); - // If getToken() fails, it will still return a dummy token that also has - // an error field containing the error message. We will send any token - // provided here and show an error if/when it is rejected by the functions - // endpoint. - return result.token; - } - return null; - } - - /** - * Calls a callable function asynchronously and returns the result. - * @param name The name of the callable trigger. - * @param data The data to pass as params to the function.s - */ - private async call( - name: string, - data: unknown, - options: HttpsCallableOptions - ): Promise { - const url = this._url(name); - - // Encode any special types, such as dates, in the input data. - data = this.serializer.encode(data); - const body = { data }; - - // Add a header for the authToken. - const headers: { [key: string]: string } = {}; - const context = await this.contextProvider.getContext(); - if (context.authToken) { - headers['Authorization'] = 'Bearer ' + context.authToken; - } - if (context.instanceIdToken) { - headers['Firebase-Instance-ID-Token'] = context.instanceIdToken; - } - - // Default timeout to 70s, but let the options override it. - const timeout = options.timeout || 70000; - - const { timer, promise: failAfterPromise } = failAfter(timeout); - - const response = await Promise.race([ - clearTimeoutWrapper(timer, this.postJSON(url, body, headers)), - failAfterPromise, - clearTimeoutWrapper(timer, this.cancelAllRequests) - ]); - - // If service was deleted, interrupted response throws an error. - if (!response) { - throw new HttpsErrorImpl( - 'cancelled', - 'Firebase Functions instance was deleted.' - ); - } - - // Check for an error status, regardless of http status. - const error = _errorForResponse( - response.status, - response.json, - this.serializer - ); - if (error) { - throw error; - } - - if (!response.json) { - throw new HttpsErrorImpl( - 'internal', - 'Response is not valid JSON object.' - ); - } - - let responseData = response.json.data; - // TODO(klimt): For right now, allow "result" instead of "data", for - // backwards compatibility. - if (typeof responseData === 'undefined') { - responseData = response.json.result; - } - if (typeof responseData === 'undefined') { - // Consider the response malformed. - throw new HttpsErrorImpl('internal', 'Response is missing data field.'); - } - - // Decode any special types, such as dates, in the returned data. - const decodedData = this.serializer.decode(responseData); - - return { data: decodedData }; - } -} - -async function clearTimeoutWrapper( - timer: number | NodeJS.Timeout, - promise: Promise -): Promise { - const result = await promise; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - clearTimeout(timer as any); - return result; -} diff --git a/packages-exp/functions-exp/src/callable.test.ts b/packages/functions/src/callable.test.ts similarity index 99% rename from packages-exp/functions-exp/src/callable.test.ts rename to packages/functions/src/callable.test.ts index e98105398cf..0de1334b655 100644 --- a/packages-exp/functions-exp/src/callable.test.ts +++ b/packages/functions/src/callable.test.ts @@ -16,7 +16,7 @@ */ import { expect } from 'chai'; import * as sinon from 'sinon'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { FunctionsErrorCode } from './public-types'; import { Provider, diff --git a/packages/functions/src/config.ts b/packages/functions/src/config.ts index b701aec8caf..fb83455310e 100644 --- a/packages/functions/src/config.ts +++ b/packages/functions/src/config.ts @@ -15,41 +15,38 @@ * limitations under the License. */ -import { Service } from './api/service'; +import { _registerComponent } from '@firebase/app'; +import { FunctionsService } from './service'; import { Component, ComponentType, ComponentContainer, - InstanceFactoryOptions + InstanceFactory } from '@firebase/component'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; +import { FUNCTIONS_TYPE } from './constants'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; +import { MessagingInternalComponentName } from '@firebase/messaging-interop-types'; -/** - * Type constant for Firebase Functions. - */ -const FUNCTIONS_TYPE = 'functions'; - -export function registerFunctions( - instance: _FirebaseNamespace, - fetchImpl: typeof fetch -): void { - const namespaceExports = { - // no-inline - Functions: Service - }; +const AUTH_INTERNAL_NAME: FirebaseAuthInternalName = 'auth-internal'; +const APP_CHECK_INTERNAL_NAME: AppCheckInternalComponentName = + 'app-check-internal'; +const MESSAGING_INTERNAL_NAME: MessagingInternalComponentName = + 'messaging-internal'; - function factory( +export function registerFunctions(fetchImpl: typeof fetch): void { + const factory: InstanceFactory<'functions'> = ( container: ComponentContainer, - { instanceIdentifier: regionOrCustomDomain }: InstanceFactoryOptions - ): Service { + { instanceIdentifier: regionOrCustomDomain } + ) => { // Dependencies const app = container.getProvider('app').getImmediate(); - const authProvider = container.getProvider('auth-internal'); - const appCheckProvider = container.getProvider('app-check-internal'); - const messagingProvider = container.getProvider('messaging'); + const authProvider = container.getProvider(AUTH_INTERNAL_NAME); + const messagingProvider = container.getProvider(MESSAGING_INTERNAL_NAME); + const appCheckProvider = container.getProvider(APP_CHECK_INTERNAL_NAME); // eslint-disable-next-line @typescript-eslint/no-explicit-any - return new Service( + return new FunctionsService( app, authProvider, messagingProvider, @@ -57,10 +54,13 @@ export function registerFunctions( regionOrCustomDomain, fetchImpl ); - } - instance.INTERNAL.registerComponent( - new Component(FUNCTIONS_TYPE, factory, ComponentType.PUBLIC) - .setServiceProps(namespaceExports) - .setMultipleInstances(true) + }; + + _registerComponent( + new Component( + FUNCTIONS_TYPE, + factory, + ComponentType.PUBLIC + ).setMultipleInstances(true) ); } diff --git a/packages-exp/functions-exp/src/constants.ts b/packages/functions/src/constants.ts similarity index 93% rename from packages-exp/functions-exp/src/constants.ts rename to packages/functions/src/constants.ts index 18e029bfd03..bbb08917fe9 100644 --- a/packages-exp/functions-exp/src/constants.ts +++ b/packages/functions/src/constants.ts @@ -18,4 +18,4 @@ /** * Type constant for Firebase Functions. */ -export const FUNCTIONS_TYPE = 'functions-exp'; +export const FUNCTIONS_TYPE = 'functions'; diff --git a/packages/functions/src/context.ts b/packages/functions/src/context.ts index 71b66bca99d..65c0283ec7a 100644 --- a/packages/functions/src/context.ts +++ b/packages/functions/src/context.ts @@ -1,7 +1,3 @@ -import { - FirebaseAuthInternal, - FirebaseAuthInternalName -} from '@firebase/auth-interop-types'; /** * @license * Copyright 2017 Google LLC @@ -18,30 +14,43 @@ import { * See the License for the specific language governing permissions and * limitations under the License. */ -import { - FirebaseMessaging, - FirebaseMessagingName -} from '@firebase/messaging-types'; import { Provider } from '@firebase/component'; +import { + AppCheckInternalComponentName, + FirebaseAppCheckInternal +} from '@firebase/app-check-interop-types'; +import { + MessagingInternal, + MessagingInternalComponentName +} from '@firebase/messaging-interop-types'; +import { + FirebaseAuthInternal, + FirebaseAuthInternalName +} from '@firebase/auth-interop-types'; /** * The metadata that should be supplied with function calls. + * @internal */ export interface Context { authToken?: string; - instanceIdToken?: string; + messagingToken?: string; + appCheckToken: string | null; } /** * Helper class to get metadata that should be included with a function call. + * @internal */ export class ContextProvider { private auth: FirebaseAuthInternal | null = null; - private messaging: FirebaseMessaging | null = null; + private messaging: MessagingInternal | null = null; + private appCheck: FirebaseAppCheckInternal | null = null; constructor( authProvider: Provider, - messagingProvider: Provider + messagingProvider: Provider, + appCheckProvider: Provider ) { this.auth = authProvider.getImmediate({ optional: true }); this.messaging = messagingProvider.getImmediate({ @@ -65,6 +74,15 @@ export class ContextProvider { } ); } + + if (!this.appCheck) { + appCheckProvider.get().then( + appCheck => (this.appCheck = appCheck), + () => { + /* get() never rejects */ + } + ); + } } async getAuthToken(): Promise { @@ -74,17 +92,14 @@ export class ContextProvider { try { const token = await this.auth.getToken(); - if (!token) { - return undefined; - } - return token.accessToken; + return token?.accessToken; } catch (e) { // If there's any error when trying to get the auth token, leave it off. return undefined; } } - async getInstanceIdToken(): Promise { + async getMessagingToken(): Promise { if ( !this.messaging || !('Notification' in self) || @@ -104,9 +119,22 @@ export class ContextProvider { } } + async getAppCheckToken(): Promise { + if (this.appCheck) { + const result = await this.appCheck.getToken(); + // If getToken() fails, it will still return a dummy token that also has + // an error field containing the error message. We will send any token + // provided here and show an error if/when it is rejected by the functions + // endpoint. + return result.token; + } + return null; + } + async getContext(): Promise { const authToken = await this.getAuthToken(); - const instanceIdToken = await this.getInstanceIdToken(); - return { authToken, instanceIdToken }; + const messagingToken = await this.getMessagingToken(); + const appCheckToken = await this.getAppCheckToken(); + return { authToken, messagingToken, appCheckToken }; } } diff --git a/packages-exp/functions-exp/src/error.ts b/packages/functions/src/error.ts similarity index 100% rename from packages-exp/functions-exp/src/error.ts rename to packages/functions/src/error.ts diff --git a/packages-exp/functions-exp/src/index.node.ts b/packages/functions/src/index.node.ts similarity index 94% rename from packages-exp/functions-exp/src/index.node.ts rename to packages/functions/src/index.node.ts index 5955172f350..1fa51c408d7 100644 --- a/packages-exp/functions-exp/src/index.node.ts +++ b/packages/functions/src/index.node.ts @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { registerVersion } from '@firebase/app-exp'; +import { registerVersion } from '@firebase/app'; import { registerFunctions } from './config'; import nodeFetch from 'node-fetch'; diff --git a/packages-exp/functions-exp/src/index.ts b/packages/functions/src/index.ts similarity index 94% rename from packages-exp/functions-exp/src/index.ts rename to packages/functions/src/index.ts index 903974e84a2..73033878485 100644 --- a/packages-exp/functions-exp/src/index.ts +++ b/packages/functions/src/index.ts @@ -20,7 +20,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { registerVersion } from '@firebase/app-exp'; +import { registerVersion } from '@firebase/app'; import { registerFunctions } from './config'; import { name, version } from '../package.json'; diff --git a/packages-exp/functions-exp/src/public-types.ts b/packages/functions/src/public-types.ts similarity index 98% rename from packages-exp/functions-exp/src/public-types.ts rename to packages/functions/src/public-types.ts index 5fe37052c06..675c0133ddb 100644 --- a/packages-exp/functions-exp/src/public-types.ts +++ b/packages/functions/src/public-types.ts @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; import { FirebaseError } from '@firebase/util'; /** @@ -150,6 +150,6 @@ export interface FunctionsError extends FirebaseError { declare module '@firebase/component' { interface NameServiceMapping { - 'functions-exp': Functions; + 'functions': Functions; } } diff --git a/packages-exp/functions-exp/src/serializer.test.ts b/packages/functions/src/serializer.test.ts similarity index 100% rename from packages-exp/functions-exp/src/serializer.test.ts rename to packages/functions/src/serializer.test.ts diff --git a/packages/functions/src/serializer.ts b/packages/functions/src/serializer.ts index f41784e45fb..e6247ec34d1 100644 --- a/packages/functions/src/serializer.ts +++ b/packages/functions/src/serializer.ts @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - const LONG_TYPE = 'type.googleapis.com/google.protobuf.Int64Value'; const UNSIGNED_LONG_TYPE = 'type.googleapis.com/google.protobuf.UInt64Value'; @@ -33,72 +32,78 @@ function mapValues( return result; } -export class Serializer { - // Takes data and encodes it in a JSON-friendly way, such that types such as - // Date are preserved. - encode(data: unknown): unknown { - if (data == null) { - return null; - } - if (data instanceof Number) { - data = data.valueOf(); - } - if (typeof data === 'number' && isFinite(data)) { - // Any number in JS is safe to put directly in JSON and parse as a double - // without any loss of precision. - return data; - } - if (data === true || data === false) { - return data; - } - if (Object.prototype.toString.call(data) === '[object String]') { - return data; - } - if (data instanceof Date) { - return data.toISOString(); - } - if (Array.isArray(data)) { - return data.map(x => this.encode(x)); - } - if (typeof data === 'function' || typeof data === 'object') { - return mapValues(data!, x => this.encode(x)); - } - // If we got this far, the data is not encodable. - throw new Error('Data cannot be encoded in JSON: ' + data); +/** + * Takes data and encodes it in a JSON-friendly way, such that types such as + * Date are preserved. + * @internal + * @param data - Data to encode. + */ +export function encode(data: unknown): unknown { + if (data == null) { + return null; + } + if (data instanceof Number) { + data = data.valueOf(); + } + if (typeof data === 'number' && isFinite(data)) { + // Any number in JS is safe to put directly in JSON and parse as a double + // without any loss of precision. + return data; + } + if (data === true || data === false) { + return data; + } + if (Object.prototype.toString.call(data) === '[object String]') { + return data; + } + if (data instanceof Date) { + return data.toISOString(); } + if (Array.isArray(data)) { + return data.map(x => encode(x)); + } + if (typeof data === 'function' || typeof data === 'object') { + return mapValues(data!, x => encode(x)); + } + // If we got this far, the data is not encodable. + throw new Error('Data cannot be encoded in JSON: ' + data); +} - // Takes data that's been encoded in a JSON-friendly form and returns a form - // with richer datatypes, such as Dates, etc. - decode(json: unknown): unknown { - if (json == null) { - return json; - } - if ((json as { [key: string]: unknown })['@type']) { - switch ((json as { [key: string]: unknown })['@type']) { - case LONG_TYPE: - // Fall through and handle this the same as unsigned. - case UNSIGNED_LONG_TYPE: { - // Technically, this could work return a valid number for malformed - // data if there was a number followed by garbage. But it's just not - // worth all the extra code to detect that case. - const value = Number((json as { [key: string]: unknown })['value']); - if (isNaN(value)) { - throw new Error('Data cannot be decoded from JSON: ' + json); - } - return value; - } - default: { +/** + * Takes data that's been encoded in a JSON-friendly form and returns a form + * with richer datatypes, such as Dates, etc. + * @internal + * @param json - JSON to convert. + */ +export function decode(json: unknown): unknown { + if (json == null) { + return json; + } + if ((json as { [key: string]: unknown })['@type']) { + switch ((json as { [key: string]: unknown })['@type']) { + case LONG_TYPE: + // Fall through and handle this the same as unsigned. + case UNSIGNED_LONG_TYPE: { + // Technically, this could work return a valid number for malformed + // data if there was a number followed by garbage. But it's just not + // worth all the extra code to detect that case. + const value = Number((json as { [key: string]: unknown })['value']); + if (isNaN(value)) { throw new Error('Data cannot be decoded from JSON: ' + json); } + return value; + } + default: { + throw new Error('Data cannot be decoded from JSON: ' + json); } } - if (Array.isArray(json)) { - return json.map(x => this.decode(x)); - } - if (typeof json === 'function' || typeof json === 'object') { - return mapValues(json!, x => this.decode(x)); - } - // Anything else is safe to return. - return json; } + if (Array.isArray(json)) { + return json.map(x => decode(x)); + } + if (typeof json === 'function' || typeof json === 'object') { + return mapValues(json!, x => decode(x)); + } + // Anything else is safe to return. + return json; } diff --git a/packages-exp/functions-exp/src/service.test.ts b/packages/functions/src/service.test.ts similarity index 100% rename from packages-exp/functions-exp/src/service.test.ts rename to packages/functions/src/service.test.ts diff --git a/packages-exp/functions-exp/src/service.ts b/packages/functions/src/service.ts similarity index 99% rename from packages-exp/functions-exp/src/service.ts rename to packages/functions/src/service.ts index 289311314d0..9825fd479b4 100644 --- a/packages-exp/functions-exp/src/service.ts +++ b/packages/functions/src/service.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp, _FirebaseService } from '@firebase/app-exp'; +import { FirebaseApp, _FirebaseService } from '@firebase/app'; import { HttpsCallable, HttpsCallableResult, diff --git a/packages/functions/test/browser/callable.test.ts b/packages/functions/test/browser/callable.test.ts deleted file mode 100644 index ef5bdf111eb..00000000000 --- a/packages/functions/test/browser/callable.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import { FirebaseApp } from '@firebase/app-types'; -import { makeFakeApp, createTestService } from '../utils'; -import { - FirebaseMessaging, - FirebaseMessagingName -} from '@firebase/messaging-types'; -import { - Provider, - ComponentContainer, - ComponentType, - Component -} from '@firebase/component'; - -// eslint-disable-next-line @typescript-eslint/no-require-imports -export const TEST_PROJECT = require('../../../../config/project.json'); - -describe('Firebase Functions > Call', () => { - const app: FirebaseApp = makeFakeApp({ - projectId: TEST_PROJECT.projectId, - messagingSenderId: 'messaging-sender-id' - }); - const region = 'us-central1'; - - // TODO(klimt): Move this to the cross-platform tests and delete this file, - // once instance id works there. - it('instance id', async () => { - if (!('Notification' in self)) { - console.log('No Notification API: skipping instance id test.'); - return; - } - // mock firebase messaging - const messagingMock: FirebaseMessaging = ({ - getToken: async () => 'iid' - } as unknown) as FirebaseMessaging; - const messagingProvider = new Provider( - 'messaging', - new ComponentContainer('test') - ); - messagingProvider.setComponent( - new Component('messaging', () => messagingMock, ComponentType.PRIVATE) - ); - - const functions = createTestService( - app, - region, - undefined, - messagingProvider - ); - - // Stub out the messaging method get an instance id token. - const stub = sinon.stub(messagingMock, 'getToken').callThrough(); - sinon.stub(Notification, 'permission').value('granted'); - - const func = functions.httpsCallable('instanceIdTest'); - const result = await func({}); - expect(result.data).to.deep.equal({}); - - expect(stub.callCount).to.equal(1); - stub.restore(); - }); -}); diff --git a/packages/functions/test/callable.test.ts b/packages/functions/test/callable.test.ts deleted file mode 100644 index 2a5efa27f98..00000000000 --- a/packages/functions/test/callable.test.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import { FirebaseApp } from '@firebase/app-types'; -import { FunctionsErrorCode } from '@firebase/functions-types'; -import { - Provider, - ComponentContainer, - Component, - ComponentType -} from '@firebase/component'; -import { - FirebaseAuthInternal, - FirebaseAuthInternalName -} from '@firebase/auth-interop-types'; -import { makeFakeApp, createTestService, getFetchImpl } from './utils'; -import { spy } from 'sinon'; - -// eslint-disable-next-line @typescript-eslint/no-require-imports -export const TEST_PROJECT = require('../../../config/project.json'); - -// Chai doesn't handle Error comparisons in a useful way. -// https://github.com/chaijs/chai/issues/608 -async function expectError( - promise: Promise, - code: FunctionsErrorCode, - message: string, - details?: any -): Promise { - let failed = false; - try { - await promise; - } catch (e) { - failed = true; - expect(e.code).to.equal(code); - expect(e.message).to.equal(message); - expect(e.details).to.deep.equal(details); - } - if (!failed) { - expect(false, 'Promise should have failed.').to.be.true; - } -} - -describe('Firebase Functions > Call', () => { - let app: FirebaseApp; - const region = 'us-central1'; - let fetchSpy: sinon.SinonSpy = spy(); - - before(() => { - const useEmulator = !!process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN; - const projectId = useEmulator - ? 'functions-integration-test' - : TEST_PROJECT.projectId; - const messagingSenderId = 'messaging-sender-id'; - - app = makeFakeApp({ projectId, messagingSenderId }); - }); - - it('sends app check header', async () => { - const spyFetchImpl = getFetchImpl(); - fetchSpy = spy(spyFetchImpl); - const functions = createTestService( - app, - region, - undefined, - undefined, - 'not-dummy-token', - fetchSpy - ); - const data = { - bool: true, - int: 2, - str: 'four', - array: [5, 6], - null: null - }; - - const func = functions.httpsCallable('dataTest'); - await func(data); - - expect(fetchSpy.args[0][1].headers['X-Firebase-AppCheck']).to.equal( - 'not-dummy-token' - ); - }); - - it('simple data', async () => { - const functions = createTestService(app, region); - // TODO(klimt): Should we add an API to create a "long" in JS? - const data = { - bool: true, - int: 2, - str: 'four', - array: [5, 6], - null: null - }; - - const func = functions.httpsCallable('dataTest'); - const result = await func(data); - - expect(result.data).to.deep.equal({ - message: 'stub response', - code: 42, - long: 420 - }); - }); - - it('scalars', async () => { - const functions = createTestService(app, region); - const func = functions.httpsCallable('scalarTest'); - const result = await func(17); - expect(result.data).to.equal(76); - }); - - it('token', async () => { - // mock auth-internal service - const authMock: FirebaseAuthInternal = ({ - getToken: async () => ({ accessToken: 'token' }) - } as unknown) as FirebaseAuthInternal; - const authProvider = new Provider( - 'auth-internal', - new ComponentContainer('test') - ); - authProvider.setComponent( - new Component('auth-internal', () => authMock, ComponentType.PRIVATE) - ); - - const functions = createTestService(app, region, authProvider); - - // Stub out the internals to get an auth token. - const stub = sinon.stub(authMock, 'getToken').callThrough(); - const func = functions.httpsCallable('tokenTest'); - const result = await func({}); - expect(result.data).to.deep.equal({}); - - expect(stub.callCount).to.equal(1); - stub.restore(); - }); - - // TODO(klimt): Add this back when instance id works on node. - /* - it('instance id', async () => { - // Stub out the messaging method get an instance id token. - const messaging = (firebase as any).messaging(app); - const stub = sinon.stub(messaging, 'getToken'); - stub.returns(Promise.resolve('iid')); - - const func = functions.httpsCallable('instanceIdTest'); - const result = await func({}); - expect(result.data).to.deep.equal({}); - - expect(stub.callCount).to.equal(1); - stub.restore(); - }); - */ - - it('null', async () => { - const functions = createTestService(app, region); - const func = functions.httpsCallable('nullTest'); - let result = await func(null); - expect(result.data).to.be.null; - - // Test with void arguments version. - result = await func(); - expect(result.data).to.be.null; - }); - - it('missing result', async () => { - const functions = createTestService(app, region); - const func = functions.httpsCallable('missingResultTest'); - await expectError(func(), 'internal', 'Response is missing data field.'); - }); - - it('unhandled error', async () => { - const functions = createTestService(app, region); - const func = functions.httpsCallable('unhandledErrorTest'); - await expectError(func(), 'internal', 'internal'); - }); - - it('unknown error', async () => { - const functions = createTestService(app, region); - const func = functions.httpsCallable('unknownErrorTest'); - await expectError(func(), 'internal', 'internal'); - }); - - it('explicit error', async () => { - const functions = createTestService(app, region); - const func = functions.httpsCallable('explicitErrorTest'); - await expectError(func(), 'out-of-range', 'explicit nope', { - start: 10, - end: 20, - long: 30 - }); - }); - - it('http error', async () => { - const functions = createTestService(app, region); - const func = functions.httpsCallable('httpErrorTest'); - await expectError(func(), 'invalid-argument', 'invalid-argument'); - }); - - it('timeout', async () => { - const functions = createTestService(app, region); - const func = functions.httpsCallable('timeoutTest', { timeout: 10 }); - await expectError(func(), 'deadline-exceeded', 'deadline-exceeded'); - }); - - it('cancels timeout', async () => { - const functions = createTestService(app, region); - const globalObj = typeof window !== 'undefined' ? window : global; - const clearTimeoutSpy = sinon.spy(globalObj, 'clearTimeout'); - const func = functions.httpsCallable('nullTest'); - await func(null); - expect(clearTimeoutSpy.called).to.be.true; - clearTimeoutSpy.restore(); - }); -}); diff --git a/packages/functions/test/serializer.test.ts b/packages/functions/test/serializer.test.ts deleted file mode 100644 index cbdadff05e5..00000000000 --- a/packages/functions/test/serializer.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { expect } from 'chai'; -import { Serializer } from '../src/serializer'; - -describe('Serializer', () => { - const serializer = new Serializer(); - - it('encodes null', () => { - expect(serializer.encode(null)).to.be.null; - expect(serializer.encode(undefined)).to.be.null; - }); - - it('decodes null', () => { - expect(serializer.decode(null)).to.be.null; - }); - - it('encodes int', () => { - expect(serializer.encode(1)).to.equal(1); - // Number isn't allowed in our own codebase, but we need to test it, in case - // a user passes one in. There's no reason not to support it, and we don't - // want to unintentionally encode them as {}. - // eslint-disable-next-line no-new-wrappers - expect(serializer.encode(new Number(1))).to.equal(1); - }); - - it('decodes int', () => { - expect(serializer.decode(1)).to.equal(1); - }); - - it('encodes long', () => { - expect(serializer.encode(-9223372036854775000)).to.equal( - -9223372036854775000 - ); - }); - - it('decodes long', () => { - expect( - serializer.decode({ - '@type': 'type.googleapis.com/google.protobuf.Int64Value', - value: '-9223372036854775000' - }) - ).to.equal(-9223372036854775000); - }); - - it('encodes unsigned long', () => { - expect(serializer.encode(9223372036854800000)).to.equal( - 9223372036854800000 - ); - }); - - it('decodes unsigned long', () => { - expect( - serializer.decode({ - '@type': 'type.googleapis.com/google.protobuf.UInt64Value', - value: '9223372036854800000' - }) - ).to.equal(9223372036854800000); - }); - - it('encodes double', () => { - expect(serializer.encode(1.2)).to.equal(1.2); - }); - - it('decodes double', () => { - expect(serializer.decode(1.2)).to.equal(1.2); - }); - - it('encodes string', () => { - expect(serializer.encode('hello')).to.equal('hello'); - }); - - it('decodes string', () => { - expect(serializer.decode('hello')).to.equal('hello'); - }); - - it('encodes date to ISO string', () => { - expect(serializer.encode(new Date(1620666095891))).to.equal( - '2021-05-10T17:01:35.891Z' - ); - }); - - it('decodes date string without modifying it', () => { - expect(serializer.decode('2021-05-10T17:01:35.891Z')).to.equal( - '2021-05-10T17:01:35.891Z' - ); - }); - - // TODO(klimt): Make this test more interesting once we have a complex type - // that can be created in JavaScript. - it('encodes array', () => { - expect(serializer.encode([1, '2', [3, 4]])).to.deep.equal([1, '2', [3, 4]]); - }); - - it('decodes array', () => { - expect( - serializer.decode([ - 1, - '2', - [ - 3, - { - value: '1099511627776', - '@type': 'type.googleapis.com/google.protobuf.Int64Value' - } - ] - ]) - ).to.deep.equal([1, '2', [3, 1099511627776]]); - }); - - // TODO(klimt): Make this test more interesting once we have a complex type - // that can be created in JavaScript. - it('encodes object', () => { - expect( - serializer.encode({ - foo: 1, - bar: 'hello', - baz: [1, 2, 3], - date: new Date(1620666095891) - }) - ).to.deep.equal({ - foo: 1, - bar: 'hello', - baz: [1, 2, 3], - date: '2021-05-10T17:01:35.891Z' - }); - }); - - it('decodes object', () => { - expect( - serializer.decode({ - foo: 1, - bar: 'hello', - baz: [ - 1, - 2, - { - value: '1099511627776', - '@type': 'type.googleapis.com/google.protobuf.Int64Value' - } - ], - date: '2021-05-10T17:01:35.891Z' - }) - ).to.deep.equal({ - foo: 1, - bar: 'hello', - baz: [1, 2, 1099511627776], - date: '2021-05-10T17:01:35.891Z' - }); - }); - - it('fails to encode NaN', () => { - expect(() => serializer.encode(NaN)).to.throw(); - }); - - it('fails to decode unknown type', () => { - expect(() => - serializer.decode({ - '@type': 'unknown', - value: 'should be ignored' - }) - ).to.throw(); - }); -}); diff --git a/packages/functions/test/service.test.ts b/packages/functions/test/service.test.ts deleted file mode 100644 index 6704e34726d..00000000000 --- a/packages/functions/test/service.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; -import { createTestService } from './utils'; - -describe('Firebase Functions > Service', () => { - describe('simple constructor', () => { - const app: any = { - options: { - projectId: 'my-project' - } - }; - const service = createTestService(app); - - it('has valid urls', () => { - assert.equal( - service._url('foo'), - 'https://us-central1-my-project.cloudfunctions.net/foo' - ); - }); - - it('can use emulator (deprecated)', () => { - service.useFunctionsEmulator('http://localhost:5005'); - assert.equal( - service._url('foo'), - 'http://localhost:5005/my-project/us-central1/foo' - ); - }); - - it('can use emulator', () => { - service.useEmulator('localhost', 5005); - assert.equal( - service._url('foo'), - 'http://localhost:5005/my-project/us-central1/foo' - ); - }); - }); - - describe('custom region/domain constructor', () => { - const app: any = { - options: { - projectId: 'my-project' - } - }; - - it('can use custom region', () => { - const service = createTestService(app, 'my-region'); - assert.equal( - service._url('foo'), - 'https://my-region-my-project.cloudfunctions.net/foo' - ); - }); - - it('can use custom domain', () => { - const service = createTestService(app, 'https://mydomain.com'); - assert.equal(service._url('foo'), 'https://mydomain.com/foo'); - }); - - it('prefers emulator to custom domain (deprecated)', () => { - const service = createTestService(app, 'https://mydomain.com'); - service.useFunctionsEmulator('http://localhost:5005'); - assert.equal( - service._url('foo'), - 'http://localhost:5005/my-project/us-central1/foo' - ); - }); - - it('prefers emulator to custom domain', () => { - const service = createTestService(app, 'https://mydomain.com'); - service.useEmulator('localhost', 5005); - assert.equal( - service._url('foo'), - 'http://localhost:5005/my-project/us-central1/foo' - ); - }); - }); -}); diff --git a/packages/functions/test/utils.ts b/packages/functions/test/utils.ts index 2a9f725a470..db429112ba9 100644 --- a/packages/functions/test/utils.ts +++ b/packages/functions/test/utils.ts @@ -15,86 +15,68 @@ * limitations under the License. */ -import { FirebaseOptions, FirebaseApp } from '@firebase/app-types'; -import { - Provider, - ComponentContainer, - Component, - ComponentType -} from '@firebase/component'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { FirebaseMessagingName } from '@firebase/messaging-types'; -import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -import { Service } from '../src/api/service'; -import nodeFetch from 'node-fetch'; - -export function makeFakeApp(options: FirebaseOptions = {}): FirebaseApp { - options = { - apiKey: 'apiKey', - projectId: 'projectId', - authDomain: 'authDomain', - messagingSenderId: '1234567890', - databaseURL: 'databaseUrl', - storageBucket: 'storageBucket', - appId: '1:777777777777:web:d93b5ca1475efe57', - ...options - }; - return { - name: 'appName', - options, - automaticDataCollectionEnabled: true, - delete: async () => {} - }; -} - -export function getFetchImpl(): typeof fetch { - return typeof window !== 'undefined' - ? fetch.bind(window) - : (nodeFetch as any); -} - -export function createTestService( - app: FirebaseApp, - regionOrCustomDomain?: string, - authProvider = new Provider( - 'auth-internal', - new ComponentContainer('test') - ), - messagingProvider = new Provider( - 'messaging', - new ComponentContainer('test') - ), - fakeAppCheckToken = 'dummytoken', - fetchImpl = getFetchImpl() -): Service { - const appCheckProvider = new Provider( - 'app-check-internal', - new ComponentContainer('test') - ); - appCheckProvider.setComponent( - new Component( - 'app-check-internal', - () => { - return { - getToken: () => Promise.resolve({ token: fakeAppCheckToken }) - } as any; // eslint-disable-line @typescript-eslint/no-explicit-any - }, - ComponentType.PRIVATE - ) - ); - const functions = new Service( - app, - authProvider, - messagingProvider, - appCheckProvider, - regionOrCustomDomain, - fetchImpl - ); - const useEmulator = !!process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN; - if (useEmulator) { - functions.useFunctionsEmulator( - process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN! - ); - } - return functions; -} + import { FirebaseOptions, FirebaseApp } from '@firebase/app'; + import { Provider, ComponentContainer } from '@firebase/component'; + import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; + import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; + import { FunctionsService } from '../src/service'; + import { connectFunctionsEmulator } from '../src/api'; + import nodeFetch from 'node-fetch'; + import { MessagingInternalComponentName } from '../../../packages/messaging-interop-types'; + + export function makeFakeApp(options: FirebaseOptions = {}): FirebaseApp { + options = { + apiKey: 'apiKey', + projectId: 'projectId', + authDomain: 'authDomain', + messagingSenderId: '1234567890', + databaseURL: 'databaseUrl', + storageBucket: 'storageBucket', + appId: '1:777777777777:web:d93b5ca1475efe57', + ...options + }; + return { + name: 'appName', + options, + automaticDataCollectionEnabled: true + }; + } + + export function createTestService( + app: FirebaseApp, + region?: string, + authProvider = new Provider( + 'auth-internal', + new ComponentContainer('test') + ), + messagingProvider = new Provider( + 'messaging-internal', + new ComponentContainer('test') + ), + appCheckProvider = new Provider( + 'app-check-internal', + new ComponentContainer('test') + ) + ): FunctionsService { + const fetchImpl: typeof fetch = + typeof window !== 'undefined' ? fetch.bind(window) : (nodeFetch as any); + const functions = new FunctionsService( + app, + authProvider, + messagingProvider, + appCheckProvider, + region, + fetchImpl + ); + const useEmulator = !!process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN; + if (useEmulator) { + const url = new URL(process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN!); + connectFunctionsEmulator( + functions, + url.hostname, + Number.parseInt(url.port, 10) + ); + } + return functions; + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/.eslintrc.js b/packages/installations-compat/.eslintrc.js similarity index 100% rename from packages-exp/firebase-exp/.eslintrc.js rename to packages/installations-compat/.eslintrc.js diff --git a/packages/installations-compat/README.md b/packages/installations-compat/README.md new file mode 100644 index 00000000000..9d1a6511c3e --- /dev/null +++ b/packages/installations-compat/README.md @@ -0,0 +1,5 @@ +# @firebase/installations-compat + +This is a compatability layer for the Firebase Installations SDK + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** \ No newline at end of file diff --git a/packages-exp/installations-compat/karma.conf.js b/packages/installations-compat/karma.conf.js similarity index 100% rename from packages-exp/installations-compat/karma.conf.js rename to packages/installations-compat/karma.conf.js diff --git a/packages-exp/installations-compat/package.json b/packages/installations-compat/package.json similarity index 94% rename from packages-exp/installations-compat/package.json rename to packages/installations-compat/package.json index d0a62b8a2f3..cda85abb078 100644 --- a/packages-exp/installations-compat/package.json +++ b/packages/installations-compat/package.json @@ -1,7 +1,6 @@ { "name": "@firebase/installations-compat", "version": "0.0.900", - "private": true, "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", "module": "dist/index.esm2017.js", @@ -28,7 +27,7 @@ "serve:host": "http-server -c-1 test-app" }, "repository": { - "directory": "packages-exp/installations-compat", + "directory": "packages/installations-compat", "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, @@ -49,7 +48,7 @@ "@firebase/app-compat": "0.x" }, "dependencies": { - "@firebase/installations-exp": "0.0.900", + "@firebase/installations": "0.4.32", "@firebase/installations-types": "0.3.4", "@firebase/util": "1.3.0", "@firebase/component": "0.5.6", diff --git a/packages-exp/installations-compat/rollup.shared.js b/packages/installations-compat/rollup.config.js similarity index 67% rename from packages-exp/installations-compat/rollup.shared.js rename to packages/installations-compat/rollup.config.js index 9b9b1f6cf38..b8de29ff5b0 100644 --- a/packages-exp/installations-compat/rollup.shared.js +++ b/packages/installations-compat/rollup.config.js @@ -14,6 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; import pkg from './package.json'; const deps = Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }); @@ -21,21 +25,36 @@ const deps = Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }); /** * ES5 Builds */ -export const es5BuildsNoPlugin = [ +const es5BuildPlugins = [typescriptPlugin({ typescript }), json()]; + +const es5Builds = [ { input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, { file: pkg.esm5, format: 'es', sourcemap: true } ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins } ]; /** * ES2017 Builds */ -export const es2017BuildsNoPlugin = [ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = [ { input: 'src/index.ts', output: { @@ -43,6 +62,9 @@ export const es2017BuildsNoPlugin = [ format: 'es', sourcemap: true }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins } ]; + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/installations-compat/src/index.ts b/packages/installations-compat/src/index.ts similarity index 97% rename from packages-exp/installations-compat/src/index.ts rename to packages/installations-compat/src/index.ts index 56475aba6c2..59774b67b62 100644 --- a/packages-exp/installations-compat/src/index.ts +++ b/packages/installations-compat/src/index.ts @@ -34,7 +34,7 @@ function registerInstallations(instance: _FirebaseNamespace): void { container => { const app = container.getProvider('app-compat').getImmediate()!; const installations = container - .getProvider('installations-exp') + .getProvider('installations') .getImmediate()!; return new InstallationsCompat(app, installations); }, diff --git a/packages-exp/installations-compat/src/installationsCompat.test.ts b/packages/installations-compat/src/installationsCompat.test.ts similarity index 97% rename from packages-exp/installations-compat/src/installationsCompat.test.ts rename to packages/installations-compat/src/installationsCompat.test.ts index 1f3442c4d86..2c6ae7063f7 100644 --- a/packages-exp/installations-compat/src/installationsCompat.test.ts +++ b/packages/installations-compat/src/installationsCompat.test.ts @@ -18,7 +18,7 @@ import './testing/setup'; import { getFakeApp, getFakeInstallations } from './testing/util'; import { InstallationsCompat } from './installationsCompat'; -import * as modularApi from '@firebase/installations-exp'; +import * as modularApi from '@firebase/installations'; import { expect } from 'chai'; import { stub } from 'sinon'; diff --git a/packages-exp/installations-compat/src/installationsCompat.ts b/packages/installations-compat/src/installationsCompat.ts similarity index 97% rename from packages-exp/installations-compat/src/installationsCompat.ts rename to packages/installations-compat/src/installationsCompat.ts index 47cd71bc337..9b811d53edd 100644 --- a/packages-exp/installations-compat/src/installationsCompat.ts +++ b/packages/installations-compat/src/installationsCompat.ts @@ -25,7 +25,7 @@ import { IdChangeCallbackFn, IdChangeUnsubscribeFn, onIdChange -} from '@firebase/installations-exp'; +} from '@firebase/installations'; export class InstallationsCompat implements FirebaseInstallationsCompat, _FirebaseService diff --git a/packages-exp/installations-compat/src/testing/setup.ts b/packages/installations-compat/src/testing/setup.ts similarity index 100% rename from packages-exp/installations-compat/src/testing/setup.ts rename to packages/installations-compat/src/testing/setup.ts diff --git a/packages-exp/installations-compat/src/testing/util.ts b/packages/installations-compat/src/testing/util.ts similarity index 94% rename from packages-exp/installations-compat/src/testing/util.ts rename to packages/installations-compat/src/testing/util.ts index 9589e6892b6..a6bc1973242 100644 --- a/packages-exp/installations-compat/src/testing/util.ts +++ b/packages/installations-compat/src/testing/util.ts @@ -16,7 +16,7 @@ */ import { FirebaseApp } from '@firebase/app-compat'; -import { Installations } from '@firebase/installations-exp'; +import { Installations } from '@firebase/installations'; const appName = 'testApp'; const apiKey = 'AIzaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaA'; @@ -52,5 +52,5 @@ export function getFakeInstallations(): Installations { }, platformLoggerProvider: null, _delete: () => Promise.resolve() - }; + } as Installations; } diff --git a/packages-exp/installations-compat/tsconfig.json b/packages/installations-compat/tsconfig.json similarity index 100% rename from packages-exp/installations-compat/tsconfig.json rename to packages/installations-compat/tsconfig.json diff --git a/packages/installations-types/index.d.ts b/packages/installations-types/index.d.ts index 5508464a509..80cfccfc74b 100644 --- a/packages/installations-types/index.d.ts +++ b/packages/installations-types/index.d.ts @@ -49,6 +49,6 @@ export type FirebaseInstallationsName = 'installations'; declare module '@firebase/component' { interface NameServiceMapping { - 'installations': FirebaseInstallations; + 'installations-compat': FirebaseInstallations; } } diff --git a/packages/installations/.eslintrc.js b/packages/installations/.eslintrc.js index 16276950320..ca80aa0f69a 100644 --- a/packages/installations/.eslintrc.js +++ b/packages/installations/.eslintrc.js @@ -1,3 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + module.exports = { extends: '../../config/.eslintrc.js', parserOptions: { diff --git a/packages-exp/installations-exp/api-extractor.json b/packages/installations/api-extractor.json similarity index 100% rename from packages-exp/installations-exp/api-extractor.json rename to packages/installations/api-extractor.json diff --git a/packages/installations/karma.conf.js b/packages/installations/karma.conf.js index d9f803e18d3..1699a0681ec 100644 --- a/packages/installations/karma.conf.js +++ b/packages/installations/karma.conf.js @@ -17,14 +17,19 @@ const karmaBase = require('../../config/karma.base'); -const files = ['src/**/*.test.ts']; +const files = [`src/**/*.test.ts`]; module.exports = function (config) { - config.set({ + const karmaConfig = { ...karmaBase, + // files to load into karma files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['mocha'] - }); + }; + + config.set(karmaConfig); }; module.exports.files = files; diff --git a/packages/installations/package.json b/packages/installations/package.json index 6c11c12ffdf..7e525646cd0 100644 --- a/packages/installations/package.json +++ b/packages/installations/package.json @@ -1,24 +1,35 @@ { "name": "@firebase/installations", "version": "0.4.32", - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", - "types": "dist/src/index.d.ts", "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.cjs.js", + "module": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "typings": "dist/src/index.d.ts", "license": "Apache-2.0", + "files": [ + "dist" + ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/installations --include-dependencies build", - "test": "run-p lint test:karma", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:karma", + "build:release": "yarn build && yarn typings:public", + "dev": "rollup -c -w", + "test": "yarn type-check && yarn test:karma && yarn lint", + "test:ci": "node ../../scripts/run_tests_in_ci.js", "test:karma": "karma start --single-run", "test:debug": "karma start --browsers=Chrome --auto-watch", + "type-check": "tsc -p . --noEmit", "serve": "yarn serve:build && yarn serve:host", "serve:build": "rollup -c test-app/rollup.config.js", - "serve:host": "http-server -c-1 test-app" + "serve:host": "http-server -c-1 test-app", + "api-report": "api-extractor run --local --verbose", + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/installations-public.d.ts", + "typings:internal": "node ../../scripts/exp/use_typings.js ./dist/src/index.d.ts" }, "repository": { "directory": "packages/installations", @@ -39,14 +50,13 @@ "typescript": "4.2.2" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" }, "dependencies": { - "@firebase/installations-types": "0.3.4", "@firebase/util": "1.3.0", "@firebase/component": "0.5.6", "idb": "3.0.2", "tslib": "^2.1.0" - } -} + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages/installations/rollup.config.js b/packages/installations/rollup.config.js index ca233000506..d01c2558271 100644 --- a/packages/installations/rollup.config.js +++ b/packages/installations/rollup.config.js @@ -17,10 +17,12 @@ import json from '@rollup/plugin-json'; import typescriptPlugin from 'rollup-plugin-typescript2'; -import pkg from './package.json'; import typescript from 'typescript'; +import pkg from './package.json'; -const deps = Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }); +const deps = [ + ...Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }) +]; /** * ES5 Builds @@ -32,10 +34,10 @@ const es5Builds = [ input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } + { file: pkg.esm5, format: 'es', sourcemap: true } ], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins } ]; @@ -58,12 +60,12 @@ const es2017Builds = [ { input: 'src/index.ts', output: { - file: pkg.esm2017, + file: pkg.browser, format: 'es', sourcemap: true }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins } ]; diff --git a/packages/installations/src/api/common.test.ts b/packages/installations/src/api/common.test.ts deleted file mode 100644 index 95829751ba7..00000000000 --- a/packages/installations/src/api/common.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import '../testing/setup'; -import { retryIfServerError } from './common'; - -describe('common', () => { - describe('retryIfServerError', () => { - let fetchStub: SinonStub<[], Promise>; - - beforeEach(() => { - fetchStub = stub(); - }); - - it('retries once if the server returns a 5xx error', async () => { - const expectedResponse = new Response(); - fetchStub.onCall(0).resolves(new Response(null, { status: 500 })); - fetchStub.onCall(1).resolves(expectedResponse); - - await expect(retryIfServerError(fetchStub)).to.eventually.equal( - expectedResponse - ); - expect(fetchStub).to.be.calledTwice; - }); - - it('does not retry again if the server returns a 5xx error twice', async () => { - const expectedResponse = new Response(null, { status: 500 }); - fetchStub.onCall(0).resolves(new Response(null, { status: 500 })); - fetchStub.onCall(1).resolves(expectedResponse); - fetchStub.onCall(2).resolves(new Response()); - - await expect(retryIfServerError(fetchStub)).to.eventually.equal( - expectedResponse - ); - expect(fetchStub).to.be.calledTwice; - }); - - it('does not retry if the error is not 5xx', async () => { - const expectedResponse = new Response(null, { status: 404 }); - fetchStub.resolves(expectedResponse); - - await expect(retryIfServerError(fetchStub)).to.eventually.equal( - expectedResponse - ); - expect(fetchStub).to.be.calledOnce; - }); - - it('does not retry if response is ok', async () => { - const expectedResponse = new Response(); - fetchStub.resolves(expectedResponse); - - await expect(retryIfServerError(fetchStub)).to.eventually.equal( - expectedResponse - ); - expect(fetchStub).to.be.calledOnce; - }); - }); -}); diff --git a/packages/installations/src/api/common.ts b/packages/installations/src/api/common.ts deleted file mode 100644 index 3283f764723..00000000000 --- a/packages/installations/src/api/common.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseError } from '@firebase/util'; -import { GenerateAuthTokenResponse } from '../interfaces/api-response'; -import { AppConfig } from '../interfaces/app-config'; -import { - CompletedAuthToken, - RegisteredInstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { - INSTALLATIONS_API_URL, - INTERNAL_AUTH_VERSION -} from '../util/constants'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; - -export function getInstallationsEndpoint({ projectId }: AppConfig): string { - return `${INSTALLATIONS_API_URL}/projects/${projectId}/installations`; -} - -export function extractAuthTokenInfoFromResponse( - response: GenerateAuthTokenResponse -): CompletedAuthToken { - return { - token: response.token, - requestStatus: RequestStatus.COMPLETED, - expiresIn: getExpiresInFromResponseExpiresIn(response.expiresIn), - creationTime: Date.now() - }; -} - -export async function getErrorFromResponse( - requestName: string, - response: Response -): Promise { - const responseJson: ErrorResponse = await response.json(); - const errorData = responseJson.error; - return ERROR_FACTORY.create(ErrorCode.REQUEST_FAILED, { - requestName, - serverCode: errorData.code, - serverMessage: errorData.message, - serverStatus: errorData.status - }); -} - -export function getHeaders({ apiKey }: AppConfig): Headers { - return new Headers({ - 'Content-Type': 'application/json', - Accept: 'application/json', - 'x-goog-api-key': apiKey - }); -} - -export function getHeadersWithAuth( - appConfig: AppConfig, - { refreshToken }: RegisteredInstallationEntry -): Headers { - const headers = getHeaders(appConfig); - headers.append('Authorization', getAuthorizationHeader(refreshToken)); - return headers; -} - -export interface ErrorResponse { - error: { - code: number; - message: string; - status: string; - }; -} - -/** - * Calls the passed in fetch wrapper and returns the response. - * If the returned response has a status of 5xx, re-runs the function once and - * returns the response. - */ -export async function retryIfServerError( - fn: () => Promise -): Promise { - const result = await fn(); - - if (result.status >= 500 && result.status < 600) { - // Internal Server Error. Retry request. - return fn(); - } - - return result; -} - -function getExpiresInFromResponseExpiresIn(responseExpiresIn: string): number { - // This works because the server will never respond with fractions of a second. - return Number(responseExpiresIn.replace('s', '000')); -} - -function getAuthorizationHeader(refreshToken: string): string { - return `${INTERNAL_AUTH_VERSION} ${refreshToken}`; -} diff --git a/packages/installations/src/api/create-installation-request.test.ts b/packages/installations/src/api/create-installation-request.test.ts deleted file mode 100644 index 05c0c8dfbbf..00000000000 --- a/packages/installations/src/api/create-installation-request.test.ts +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseError } from '@firebase/util'; -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import { CreateInstallationResponse } from '../interfaces/api-response'; -import { AppConfig } from '../interfaces/app-config'; -import { - InProgressInstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { compareHeaders } from '../testing/compare-headers'; -import { getFakeAppConfig } from '../testing/fake-generators'; -import '../testing/setup'; -import { - INSTALLATIONS_API_URL, - INTERNAL_AUTH_VERSION, - PACKAGE_VERSION -} from '../util/constants'; -import { ErrorResponse } from './common'; -import { createInstallationRequest } from './create-installation-request'; - -const FID = 'defenders-of-the-faith'; - -describe('createInstallationRequest', () => { - let appConfig: AppConfig; - let fetchSpy: SinonStub<[RequestInfo, RequestInit?], Promise>; - let inProgressInstallationEntry: InProgressInstallationEntry; - let response: CreateInstallationResponse; - - beforeEach(() => { - appConfig = getFakeAppConfig(); - - inProgressInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - registrationTime: Date.now() - }; - - response = { - refreshToken: 'refreshToken', - authToken: { - token: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', - expiresIn: '604800s' - }, - fid: FID - }; - fetchSpy = stub(self, 'fetch'); - }); - - describe('successful request', () => { - beforeEach(() => { - fetchSpy.resolves(new Response(JSON.stringify(response))); - }); - - it('registers a pending InstallationEntry', async () => { - const registeredInstallationEntry = await createInstallationRequest( - appConfig, - inProgressInstallationEntry - ); - expect(registeredInstallationEntry.registrationStatus).to.equal( - RequestStatus.COMPLETED - ); - }); - - it('calls the createInstallation server API with correct parameters', async () => { - const expectedHeaders = new Headers({ - 'Content-Type': 'application/json', - Accept: 'application/json', - 'x-goog-api-key': 'apiKey' - }); - const expectedBody = { - fid: FID, - authVersion: INTERNAL_AUTH_VERSION, - appId: appConfig.appId, - sdkVersion: PACKAGE_VERSION - }; - const expectedRequest: RequestInit = { - method: 'POST', - headers: expectedHeaders, - body: JSON.stringify(expectedBody) - }; - const expectedEndpoint = `${INSTALLATIONS_API_URL}/projects/projectId/installations`; - - await createInstallationRequest(appConfig, inProgressInstallationEntry); - expect(fetchSpy).to.be.calledOnceWith(expectedEndpoint, expectedRequest); - const actualHeaders = fetchSpy.lastCall.lastArg.headers; - compareHeaders(expectedHeaders, actualHeaders); - }); - }); - - it('returns the FID from the request if the response does not contain one', async () => { - response = { - refreshToken: 'refreshToken', - authToken: { - token: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', - expiresIn: '604800s' - } - }; - fetchSpy.resolves(new Response(JSON.stringify(response))); - - const registeredInstallationEntry = await createInstallationRequest( - appConfig, - inProgressInstallationEntry - ); - expect(registeredInstallationEntry.fid).to.equal(FID); - }); - - describe('failed request', () => { - it('throws a FirebaseError with the error information from the server', async () => { - const errorResponse: ErrorResponse = { - error: { - code: 409, - message: 'Requested entity already exists', - status: 'ALREADY_EXISTS' - } - }; - - fetchSpy.resolves( - new Response(JSON.stringify(errorResponse), { status: 409 }) - ); - - await expect( - createInstallationRequest(appConfig, inProgressInstallationEntry) - ).to.be.rejectedWith(FirebaseError); - }); - - it('retries once if the server returns a 5xx error', async () => { - const errorResponse: ErrorResponse = { - error: { - code: 500, - message: 'Internal server error', - status: 'SERVER_ERROR' - } - }; - - fetchSpy - .onCall(0) - .resolves(new Response(JSON.stringify(errorResponse), { status: 500 })); - fetchSpy.onCall(1).resolves(new Response(JSON.stringify(response))); - - await expect( - createInstallationRequest(appConfig, inProgressInstallationEntry) - ).to.be.fulfilled; - expect(fetchSpy).to.be.calledTwice; - }); - }); -}); diff --git a/packages/installations/src/api/create-installation-request.ts b/packages/installations/src/api/create-installation-request.ts deleted file mode 100644 index 07e0c7bb35c..00000000000 --- a/packages/installations/src/api/create-installation-request.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CreateInstallationResponse } from '../interfaces/api-response'; -import { AppConfig } from '../interfaces/app-config'; -import { - InProgressInstallationEntry, - RegisteredInstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { INTERNAL_AUTH_VERSION, PACKAGE_VERSION } from '../util/constants'; -import { - extractAuthTokenInfoFromResponse, - getErrorFromResponse, - getHeaders, - getInstallationsEndpoint, - retryIfServerError -} from './common'; - -export async function createInstallationRequest( - appConfig: AppConfig, - { fid }: InProgressInstallationEntry -): Promise { - const endpoint = getInstallationsEndpoint(appConfig); - - const headers = getHeaders(appConfig); - const body = { - fid, - authVersion: INTERNAL_AUTH_VERSION, - appId: appConfig.appId, - sdkVersion: PACKAGE_VERSION - }; - - const request: RequestInit = { - method: 'POST', - headers, - body: JSON.stringify(body) - }; - - const response = await retryIfServerError(() => fetch(endpoint, request)); - if (response.ok) { - const responseValue: CreateInstallationResponse = await response.json(); - const registeredInstallationEntry: RegisteredInstallationEntry = { - fid: responseValue.fid || fid, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: responseValue.refreshToken, - authToken: extractAuthTokenInfoFromResponse(responseValue.authToken) - }; - return registeredInstallationEntry; - } else { - throw await getErrorFromResponse('Create Installation', response); - } -} diff --git a/packages/installations/src/api/delete-installation-request.test.ts b/packages/installations/src/api/delete-installation-request.test.ts deleted file mode 100644 index 278b4965882..00000000000 --- a/packages/installations/src/api/delete-installation-request.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseError } from '@firebase/util'; -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import { AppConfig } from '../interfaces/app-config'; -import { - RegisteredInstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { compareHeaders } from '../testing/compare-headers'; -import { getFakeAppConfig } from '../testing/fake-generators'; -import '../testing/setup'; -import { - INSTALLATIONS_API_URL, - INTERNAL_AUTH_VERSION -} from '../util/constants'; -import { ErrorResponse } from './common'; -import { deleteInstallationRequest } from './delete-installation-request'; - -const FID = 'foreclosure-of-a-dream'; - -describe('deleteInstallationRequest', () => { - let appConfig: AppConfig; - let fetchSpy: SinonStub<[RequestInfo, RequestInit?], Promise>; - let registeredInstallationEntry: RegisteredInstallationEntry; - - beforeEach(() => { - appConfig = getFakeAppConfig(); - - registeredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - requestStatus: RequestStatus.NOT_STARTED - } - }; - - fetchSpy = stub(self, 'fetch'); - }); - - describe('successful request', () => { - beforeEach(() => { - fetchSpy.resolves(new Response()); - }); - - it('calls the deleteInstallation server API with correct parameters', async () => { - const expectedHeaders = new Headers({ - 'Content-Type': 'application/json', - Accept: 'application/json', - Authorization: `${INTERNAL_AUTH_VERSION} refreshToken`, - 'x-goog-api-key': 'apiKey' - }); - const expectedRequest: RequestInit = { - method: 'DELETE', - headers: expectedHeaders - }; - const expectedEndpoint = `${INSTALLATIONS_API_URL}/projects/projectId/installations/${FID}`; - - await deleteInstallationRequest(appConfig, registeredInstallationEntry); - - expect(fetchSpy).to.be.calledOnceWith(expectedEndpoint, expectedRequest); - const actualHeaders = fetchSpy.lastCall.lastArg.headers; - compareHeaders(expectedHeaders, actualHeaders); - }); - }); - - describe('failed request', () => { - it('throws a FirebaseError with the error information from the server', async () => { - const errorResponse: ErrorResponse = { - error: { - code: 409, - message: 'Requested entity already exists', - status: 'ALREADY_EXISTS' - } - }; - - fetchSpy.resolves( - new Response(JSON.stringify(errorResponse), { status: 409 }) - ); - - await expect( - deleteInstallationRequest(appConfig, registeredInstallationEntry) - ).to.be.rejectedWith(FirebaseError); - }); - - it('retries once if the server returns a 5xx error', async () => { - const errorResponse: ErrorResponse = { - error: { - code: 500, - message: 'Internal server error', - status: 'SERVER_ERROR' - } - }; - - fetchSpy - .onCall(0) - .resolves(new Response(JSON.stringify(errorResponse), { status: 500 })); - fetchSpy.onCall(1).resolves(new Response()); - - await expect( - deleteInstallationRequest(appConfig, registeredInstallationEntry) - ).to.be.fulfilled; - expect(fetchSpy).to.be.calledTwice; - }); - }); -}); diff --git a/packages/installations/src/api/delete-installation-request.ts b/packages/installations/src/api/delete-installation-request.ts deleted file mode 100644 index 5fba2314c5a..00000000000 --- a/packages/installations/src/api/delete-installation-request.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AppConfig } from '../interfaces/app-config'; -import { RegisteredInstallationEntry } from '../interfaces/installation-entry'; -import { - getErrorFromResponse, - getHeadersWithAuth, - getInstallationsEndpoint, - retryIfServerError -} from './common'; - -export async function deleteInstallationRequest( - appConfig: AppConfig, - installationEntry: RegisteredInstallationEntry -): Promise { - const endpoint = getDeleteEndpoint(appConfig, installationEntry); - - const headers = getHeadersWithAuth(appConfig, installationEntry); - const request: RequestInit = { - method: 'DELETE', - headers - }; - - const response = await retryIfServerError(() => fetch(endpoint, request)); - if (!response.ok) { - throw await getErrorFromResponse('Delete Installation', response); - } -} - -function getDeleteEndpoint( - appConfig: AppConfig, - { fid }: RegisteredInstallationEntry -): string { - return `${getInstallationsEndpoint(appConfig)}/${fid}`; -} diff --git a/packages-exp/installations-exp/src/api/delete-installations.test.ts b/packages/installations/src/api/delete-installations.test.ts similarity index 100% rename from packages-exp/installations-exp/src/api/delete-installations.test.ts rename to packages/installations/src/api/delete-installations.test.ts diff --git a/packages-exp/installations-exp/src/api/delete-installations.ts b/packages/installations/src/api/delete-installations.ts similarity index 100% rename from packages-exp/installations-exp/src/api/delete-installations.ts rename to packages/installations/src/api/delete-installations.ts diff --git a/packages/installations/src/api/generate-auth-token-request.test.ts b/packages/installations/src/api/generate-auth-token-request.test.ts deleted file mode 100644 index a7f74115681..00000000000 --- a/packages/installations/src/api/generate-auth-token-request.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseError } from '@firebase/util'; -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import { GenerateAuthTokenResponse } from '../interfaces/api-response'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; -import { - CompletedAuthToken, - RegisteredInstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { compareHeaders } from '../testing/compare-headers'; -import { getFakeDependencies } from '../testing/fake-generators'; -import '../testing/setup'; -import { - INSTALLATIONS_API_URL, - INTERNAL_AUTH_VERSION, - PACKAGE_VERSION -} from '../util/constants'; -import { ErrorResponse } from './common'; -import { generateAuthTokenRequest } from './generate-auth-token-request'; - -const FID = 'evil-has-no-boundaries'; - -describe('generateAuthTokenRequest', () => { - let dependencies: FirebaseDependencies; - let fetchSpy: SinonStub<[RequestInfo, RequestInit?], Promise>; - let registeredInstallationEntry: RegisteredInstallationEntry; - let response: GenerateAuthTokenResponse; - - beforeEach(() => { - dependencies = getFakeDependencies(); - - registeredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - requestStatus: RequestStatus.NOT_STARTED - } - }; - - response = { - token: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', - expiresIn: '604800s' - }; - - fetchSpy = stub(self, 'fetch'); - }); - - describe('successful request', () => { - beforeEach(() => { - fetchSpy.resolves(new Response(JSON.stringify(response))); - }); - - it('fetches a new Authentication Token', async () => { - const completedAuthToken: CompletedAuthToken = await generateAuthTokenRequest( - dependencies, - registeredInstallationEntry - ); - expect(completedAuthToken.requestStatus).to.equal( - RequestStatus.COMPLETED - ); - }); - - it('calls the generateAuthToken server API with correct parameters', async () => { - const expectedHeaders = new Headers({ - 'Content-Type': 'application/json', - Accept: 'application/json', - Authorization: `${INTERNAL_AUTH_VERSION} refreshToken`, - 'x-goog-api-key': 'apiKey', - 'x-firebase-client': 'a/1.2.3 b/2.3.4' - }); - const expectedBody = { - installation: { - sdkVersion: PACKAGE_VERSION - } - }; - const expectedRequest: RequestInit = { - method: 'POST', - headers: expectedHeaders, - body: JSON.stringify(expectedBody) - }; - const expectedEndpoint = `${INSTALLATIONS_API_URL}/projects/projectId/installations/${FID}/authTokens:generate`; - - await generateAuthTokenRequest(dependencies, registeredInstallationEntry); - - expect(fetchSpy).to.be.calledOnceWith(expectedEndpoint, expectedRequest); - const actualHeaders = fetchSpy.lastCall.lastArg.headers; - compareHeaders(expectedHeaders, actualHeaders); - }); - }); - - describe('failed request', () => { - it('throws a FirebaseError with the error information from the server', async () => { - const errorResponse: ErrorResponse = { - error: { - code: 409, - message: 'Requested entity already exists', - status: 'ALREADY_EXISTS' - } - }; - - fetchSpy.resolves( - new Response(JSON.stringify(errorResponse), { status: 409 }) - ); - - await expect( - generateAuthTokenRequest(dependencies, registeredInstallationEntry) - ).to.be.rejectedWith(FirebaseError); - }); - - it('retries once if the server returns a 5xx error', async () => { - const errorResponse: ErrorResponse = { - error: { - code: 500, - message: 'Internal server error', - status: 'SERVER_ERROR' - } - }; - - fetchSpy - .onCall(0) - .resolves(new Response(JSON.stringify(errorResponse), { status: 500 })); - fetchSpy.onCall(1).resolves(new Response(JSON.stringify(response))); - - await expect( - generateAuthTokenRequest(dependencies, registeredInstallationEntry) - ).to.be.fulfilled; - expect(fetchSpy).to.be.calledTwice; - }); - }); -}); diff --git a/packages/installations/src/api/generate-auth-token-request.ts b/packages/installations/src/api/generate-auth-token-request.ts deleted file mode 100644 index f32650179f6..00000000000 --- a/packages/installations/src/api/generate-auth-token-request.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { GenerateAuthTokenResponse } from '../interfaces/api-response'; -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; -import { - CompletedAuthToken, - RegisteredInstallationEntry -} from '../interfaces/installation-entry'; -import { PACKAGE_VERSION } from '../util/constants'; -import { - extractAuthTokenInfoFromResponse, - getErrorFromResponse, - getHeadersWithAuth, - getInstallationsEndpoint, - retryIfServerError -} from './common'; - -export async function generateAuthTokenRequest( - { appConfig, platformLoggerProvider }: FirebaseDependencies, - installationEntry: RegisteredInstallationEntry -): Promise { - const endpoint = getGenerateAuthTokenEndpoint(appConfig, installationEntry); - - const headers = getHeadersWithAuth(appConfig, installationEntry); - - // If platform logger exists, add the platform info string to the header. - const platformLogger = platformLoggerProvider.getImmediate({ - optional: true - }); - if (platformLogger) { - headers.append('x-firebase-client', platformLogger.getPlatformInfoString()); - } - - const body = { - installation: { - sdkVersion: PACKAGE_VERSION - } - }; - - const request: RequestInit = { - method: 'POST', - headers, - body: JSON.stringify(body) - }; - - const response = await retryIfServerError(() => fetch(endpoint, request)); - if (response.ok) { - const responseValue: GenerateAuthTokenResponse = await response.json(); - const completedAuthToken: CompletedAuthToken = extractAuthTokenInfoFromResponse( - responseValue - ); - return completedAuthToken; - } else { - throw await getErrorFromResponse('Generate Auth Token', response); - } -} - -function getGenerateAuthTokenEndpoint( - appConfig: AppConfig, - { fid }: RegisteredInstallationEntry -): string { - return `${getInstallationsEndpoint(appConfig)}/${fid}/authTokens:generate`; -} diff --git a/packages-exp/installations-exp/src/api/get-id.test.ts b/packages/installations/src/api/get-id.test.ts similarity index 100% rename from packages-exp/installations-exp/src/api/get-id.test.ts rename to packages/installations/src/api/get-id.test.ts diff --git a/packages-exp/installations-exp/src/api/get-id.ts b/packages/installations/src/api/get-id.ts similarity index 100% rename from packages-exp/installations-exp/src/api/get-id.ts rename to packages/installations/src/api/get-id.ts diff --git a/packages-exp/installations-exp/src/api/get-installations.ts b/packages/installations/src/api/get-installations.ts similarity index 97% rename from packages-exp/installations-exp/src/api/get-installations.ts rename to packages/installations/src/api/get-installations.ts index 777cb3fdc75..98ee432a747 100644 --- a/packages-exp/installations-exp/src/api/get-installations.ts +++ b/packages/installations/src/api/get-installations.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app'; import { Installations } from '../interfaces/public-types'; /** @@ -28,7 +28,7 @@ import { Installations } from '../interfaces/public-types'; export function getInstallations(app: FirebaseApp = getApp()): Installations { const installationsImpl = _getProvider( app, - 'installations-exp' + 'installations' ).getImmediate(); return installationsImpl; } diff --git a/packages-exp/installations-exp/src/api/get-token.test.ts b/packages/installations/src/api/get-token.test.ts similarity index 100% rename from packages-exp/installations-exp/src/api/get-token.test.ts rename to packages/installations/src/api/get-token.test.ts diff --git a/packages-exp/installations-exp/src/api/get-token.ts b/packages/installations/src/api/get-token.ts similarity index 100% rename from packages-exp/installations-exp/src/api/get-token.ts rename to packages/installations/src/api/get-token.ts diff --git a/packages-exp/installations-exp/src/api/index.ts b/packages/installations/src/api/index.ts similarity index 100% rename from packages-exp/installations-exp/src/api/index.ts rename to packages/installations/src/api/index.ts diff --git a/packages-exp/installations-exp/src/api/on-id-change.test.ts b/packages/installations/src/api/on-id-change.test.ts similarity index 100% rename from packages-exp/installations-exp/src/api/on-id-change.test.ts rename to packages/installations/src/api/on-id-change.test.ts diff --git a/packages-exp/installations-exp/src/api/on-id-change.ts b/packages/installations/src/api/on-id-change.ts similarity index 100% rename from packages-exp/installations-exp/src/api/on-id-change.ts rename to packages/installations/src/api/on-id-change.ts diff --git a/packages-exp/installations-exp/src/functions/common.test.ts b/packages/installations/src/functions/common.test.ts similarity index 100% rename from packages-exp/installations-exp/src/functions/common.test.ts rename to packages/installations/src/functions/common.test.ts diff --git a/packages-exp/installations-exp/src/functions/common.ts b/packages/installations/src/functions/common.ts similarity index 100% rename from packages-exp/installations-exp/src/functions/common.ts rename to packages/installations/src/functions/common.ts diff --git a/packages-exp/installations-exp/src/functions/config.ts b/packages/installations/src/functions/config.ts similarity index 82% rename from packages-exp/installations-exp/src/functions/config.ts rename to packages/installations/src/functions/config.ts index efd48f4468f..5d395f68ebd 100644 --- a/packages-exp/installations-exp/src/functions/config.ts +++ b/packages/installations/src/functions/config.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _registerComponent, _getProvider } from '@firebase/app-exp'; +import { _registerComponent, _getProvider } from '@firebase/app'; import { Component, ComponentType, @@ -27,13 +27,13 @@ import { _FirebaseInstallationsInternal } from '../interfaces/public-types'; import { FirebaseInstallationsImpl } from '../interfaces/installation-impl'; import { extractAppConfig } from '../helpers/extract-app-config'; -const INSTALLATIONS_NAME = 'installations-exp'; -const INSTALLATIONS_NAME_INTERNAL = 'installations-exp-internal'; +const INSTALLATIONS_NAME = 'installations'; +const INSTALLATIONS_NAME_INTERNAL = 'installations-internal'; -const publicFactory: InstanceFactory<'installations-exp'> = ( +const publicFactory: InstanceFactory<'installations'> = ( container: ComponentContainer ) => { - const app = container.getProvider('app-exp').getImmediate(); + const app = container.getProvider('app').getImmediate(); // Throws if app isn't configured properly. const appConfig = extractAppConfig(app); const platformLoggerProvider = _getProvider(app, 'platform-logger'); @@ -47,10 +47,10 @@ const publicFactory: InstanceFactory<'installations-exp'> = ( return installationsImpl; }; -const internalFactory: InstanceFactory<'installations-exp-internal'> = ( +const internalFactory: InstanceFactory<'installations-internal'> = ( container: ComponentContainer ) => { - const app = container.getProvider('app-exp').getImmediate(); + const app = container.getProvider('app').getImmediate(); // Internal FIS instance relies on public FIS instance. const installations = _getProvider(app, INSTALLATIONS_NAME).getImmediate(); diff --git a/packages-exp/installations-exp/src/functions/create-installation-request.test.ts b/packages/installations/src/functions/create-installation-request.test.ts similarity index 100% rename from packages-exp/installations-exp/src/functions/create-installation-request.test.ts rename to packages/installations/src/functions/create-installation-request.test.ts diff --git a/packages-exp/installations-exp/src/functions/create-installation-request.ts b/packages/installations/src/functions/create-installation-request.ts similarity index 100% rename from packages-exp/installations-exp/src/functions/create-installation-request.ts rename to packages/installations/src/functions/create-installation-request.ts diff --git a/packages-exp/installations-exp/src/functions/delete-installation-request.test.ts b/packages/installations/src/functions/delete-installation-request.test.ts similarity index 100% rename from packages-exp/installations-exp/src/functions/delete-installation-request.test.ts rename to packages/installations/src/functions/delete-installation-request.test.ts diff --git a/packages-exp/installations-exp/src/functions/delete-installation-request.ts b/packages/installations/src/functions/delete-installation-request.ts similarity index 100% rename from packages-exp/installations-exp/src/functions/delete-installation-request.ts rename to packages/installations/src/functions/delete-installation-request.ts diff --git a/packages/installations/src/functions/delete-installation.test.ts b/packages/installations/src/functions/delete-installation.test.ts deleted file mode 100644 index 376a35e53bd..00000000000 --- a/packages/installations/src/functions/delete-installation.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import * as deleteInstallationRequestModule from '../api/delete-installation-request'; -import { get, set } from '../helpers/idb-manager'; -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; -import { - InProgressInstallationEntry, - RegisteredInstallationEntry, - RequestStatus, - UnregisteredInstallationEntry -} from '../interfaces/installation-entry'; -import { getFakeDependencies } from '../testing/fake-generators'; -import '../testing/setup'; -import { ErrorCode } from '../util/errors'; -import { sleep } from '../util/sleep'; -import { deleteInstallation } from './delete-installation'; - -const FID = 'children-of-the-damned'; - -describe('deleteInstallation', () => { - let dependencies: FirebaseDependencies; - let deleteInstallationRequestSpy: SinonStub< - [AppConfig, RegisteredInstallationEntry], - Promise - >; - - beforeEach(() => { - dependencies = getFakeDependencies(); - - deleteInstallationRequestSpy = stub( - deleteInstallationRequestModule, - 'deleteInstallationRequest' - ).callsFake( - () => sleep(100) // Request would take some time - ); - }); - - it('resolves without calling server API if there is no installation', async () => { - await expect(deleteInstallation(dependencies)).to.be.fulfilled; - expect(deleteInstallationRequestSpy).not.to.have.been.called; - }); - - it('deletes and resolves without calling server API if the installation is unregistered', async () => { - const entry: UnregisteredInstallationEntry = { - registrationStatus: RequestStatus.NOT_STARTED, - fid: FID - }; - await set(dependencies.appConfig, entry); - - await expect(deleteInstallation(dependencies)).to.be.fulfilled; - expect(deleteInstallationRequestSpy).not.to.have.been.called; - await expect(get(dependencies.appConfig)).to.eventually.be.undefined; - }); - - it('rejects without calling server API if the installation is pending', async () => { - const entry: InProgressInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - registrationTime: Date.now() - 3 * 1000 - }; - await set(dependencies.appConfig, entry); - - await expect(deleteInstallation(dependencies)).to.be.rejectedWith( - ErrorCode.DELETE_PENDING_REGISTRATION - ); - expect(deleteInstallationRequestSpy).not.to.have.been.called; - }); - - it('rejects without calling server API if the installation is registered and app is offline', async () => { - const entry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: 'authToken', - expiresIn: 123456, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - } - }; - await set(dependencies.appConfig, entry); - stub(navigator, 'onLine').value(false); - - await expect(deleteInstallation(dependencies)).to.be.rejectedWith( - ErrorCode.APP_OFFLINE - ); - expect(deleteInstallationRequestSpy).not.to.have.been.called; - }); - - it('deletes and resolves after calling server API if the installation is registered', async () => { - const entry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: 'authToken', - expiresIn: 123456, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - } - }; - await set(dependencies.appConfig, entry); - - await expect(deleteInstallation(dependencies)).to.be.fulfilled; - expect(deleteInstallationRequestSpy).to.have.been.calledOnceWith( - dependencies.appConfig, - entry - ); - await expect(get(dependencies.appConfig)).to.eventually.be.undefined; - }); -}); diff --git a/packages/installations/src/functions/delete-installation.ts b/packages/installations/src/functions/delete-installation.ts deleted file mode 100644 index 53f912569cc..00000000000 --- a/packages/installations/src/functions/delete-installation.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { deleteInstallationRequest } from '../api/delete-installation-request'; -import { remove, update } from '../helpers/idb-manager'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; -import { RequestStatus } from '../interfaces/installation-entry'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; - -export async function deleteInstallation( - dependencies: FirebaseDependencies -): Promise { - const { appConfig } = dependencies; - - const entry = await update(appConfig, oldEntry => { - if (oldEntry && oldEntry.registrationStatus === RequestStatus.NOT_STARTED) { - // Delete the unregistered entry without sending a deleteInstallation request. - return undefined; - } - return oldEntry; - }); - - if (entry) { - if (entry.registrationStatus === RequestStatus.IN_PROGRESS) { - // Can't delete while trying to register. - throw ERROR_FACTORY.create(ErrorCode.DELETE_PENDING_REGISTRATION); - } else if (entry.registrationStatus === RequestStatus.COMPLETED) { - if (!navigator.onLine) { - throw ERROR_FACTORY.create(ErrorCode.APP_OFFLINE); - } else { - await deleteInstallationRequest(appConfig, entry); - await remove(appConfig); - } - } - } -} diff --git a/packages-exp/installations-exp/src/functions/generate-auth-token-request.test.ts b/packages/installations/src/functions/generate-auth-token-request.test.ts similarity index 100% rename from packages-exp/installations-exp/src/functions/generate-auth-token-request.test.ts rename to packages/installations/src/functions/generate-auth-token-request.test.ts diff --git a/packages-exp/installations-exp/src/functions/generate-auth-token-request.ts b/packages/installations/src/functions/generate-auth-token-request.ts similarity index 100% rename from packages-exp/installations-exp/src/functions/generate-auth-token-request.ts rename to packages/installations/src/functions/generate-auth-token-request.ts diff --git a/packages/installations/src/functions/get-id.test.ts b/packages/installations/src/functions/get-id.test.ts deleted file mode 100644 index a1c03700b44..00000000000 --- a/packages/installations/src/functions/get-id.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonStub, stub } from 'sinon'; -import * as getInstallationEntryModule from '../helpers/get-installation-entry'; -import * as refreshAuthTokenModule from '../helpers/refresh-auth-token'; -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; -import { - RegisteredInstallationEntry, - RequestStatus -} from '../interfaces/installation-entry'; -import { getFakeDependencies } from '../testing/fake-generators'; -import '../testing/setup'; -import { getId } from './get-id'; - -const FID = 'disciples-of-the-watch'; - -describe('getId', () => { - let dependencies: FirebaseDependencies; - let getInstallationEntrySpy: SinonStub< - [AppConfig], - Promise - >; - - beforeEach(() => { - dependencies = getFakeDependencies(); - - getInstallationEntrySpy = stub( - getInstallationEntryModule, - 'getInstallationEntry' - ); - }); - - it('returns the FID in InstallationEntry returned by getInstallationEntry', async () => { - getInstallationEntrySpy.resolves({ - installationEntry: { - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }, - registrationPromise: Promise.resolve({} as RegisteredInstallationEntry) - }); - - const fid = await getId(dependencies); - expect(fid).to.equal(FID); - expect(getInstallationEntrySpy).to.be.calledOnce; - }); - - it('calls refreshAuthToken if the installation is registered', async () => { - getInstallationEntrySpy.resolves({ - installationEntry: { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - requestStatus: RequestStatus.NOT_STARTED - } - } - }); - - const refreshAuthTokenSpy = stub( - refreshAuthTokenModule, - 'refreshAuthToken' - ).resolves({ - token: 'authToken', - expiresIn: 123456, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - }); - - await getId(dependencies); - expect(refreshAuthTokenSpy).to.be.calledOnce; - }); -}); diff --git a/packages/installations/src/functions/get-id.ts b/packages/installations/src/functions/get-id.ts deleted file mode 100644 index 28a692af10f..00000000000 --- a/packages/installations/src/functions/get-id.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getInstallationEntry } from '../helpers/get-installation-entry'; -import { refreshAuthToken } from '../helpers/refresh-auth-token'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; - -export async function getId( - dependencies: FirebaseDependencies -): Promise { - const { installationEntry, registrationPromise } = await getInstallationEntry( - dependencies.appConfig - ); - - if (registrationPromise) { - registrationPromise.catch(console.error); - } else { - // If the installation is already registered, update the authentication - // token if needed. - refreshAuthToken(dependencies).catch(console.error); - } - - return installationEntry.fid; -} diff --git a/packages/installations/src/functions/get-token.test.ts b/packages/installations/src/functions/get-token.test.ts deleted file mode 100644 index be633924e05..00000000000 --- a/packages/installations/src/functions/get-token.test.ts +++ /dev/null @@ -1,465 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { SinonFakeTimers, SinonStub, stub, useFakeTimers } from 'sinon'; -import * as createInstallationRequestModule from '../api/create-installation-request'; -import * as generateAuthTokenRequestModule from '../api/generate-auth-token-request'; -import { get, set } from '../helpers/idb-manager'; -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; -import { - CompletedAuthToken, - InProgressInstallationEntry, - RegisteredInstallationEntry, - RequestStatus, - UnregisteredInstallationEntry -} from '../interfaces/installation-entry'; -import { getFakeDependencies } from '../testing/fake-generators'; -import '../testing/setup'; -import { TOKEN_EXPIRATION_BUFFER } from '../util/constants'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { sleep } from '../util/sleep'; -import { getToken } from './get-token'; - -const FID = 'dont-talk-to-strangers'; -const AUTH_TOKEN = 'authToken'; -const NEW_AUTH_TOKEN = 'newAuthToken'; -const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000; - -/** - * A map of different states of the database and a function that creates the - * said state. - */ -const setupInstallationEntryMap: Map< - string, - (appConfig: AppConfig) => Promise -> = new Map([ - [ - 'existing and valid auth token', - async (appConfig: AppConfig) => { - const entry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - } - }; - await set(appConfig, entry); - } - ], - [ - 'expired auth token', - async (appConfig: AppConfig) => { - const entry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - 2 * ONE_WEEK_MS - } - }; - await set(appConfig, entry); - } - ], - [ - 'pending auth token', - async (appConfig: AppConfig) => { - const entry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - requestStatus: RequestStatus.IN_PROGRESS, - requestTime: Date.now() - 3 * 1000 - } - }; - - await set(appConfig, entry); - - // Finish pending request after 500 ms - // eslint-disable-next-line @typescript-eslint/no-floating-promises - sleep(500).then(async () => { - const updatedEntry: RegisteredInstallationEntry = { - ...entry, - authToken: { - token: NEW_AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - } - }; - await set(appConfig, updatedEntry); - }); - } - ], - [ - 'no auth token', - async (appConfig: AppConfig) => { - const entry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - requestStatus: RequestStatus.NOT_STARTED - } - }; - await set(appConfig, entry); - } - ], - [ - 'pending fid registration', - async (appConfig: AppConfig) => { - const entry: InProgressInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.IN_PROGRESS, - registrationTime: Date.now() - 3 * 1000 - }; - - await set(appConfig, entry); - - // Finish pending request after 500 ms - // eslint-disable-next-line @typescript-eslint/no-floating-promises - sleep(500).then(async () => { - const updatedEntry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - } - }; - await set(appConfig, updatedEntry); - }); - } - ], - [ - 'unregistered fid', - async (appConfig: AppConfig) => { - const entry: UnregisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.NOT_STARTED - }; - - await set(appConfig, entry); - } - ] -]); - -describe('getToken', () => { - let dependencies: FirebaseDependencies; - let createInstallationRequestSpy: SinonStub< - [AppConfig, InProgressInstallationEntry], - Promise - >; - let generateAuthTokenRequestSpy: SinonStub< - [FirebaseDependencies, RegisteredInstallationEntry], - Promise - >; - - beforeEach(() => { - dependencies = getFakeDependencies(); - - createInstallationRequestSpy = stub( - createInstallationRequestModule, - 'createInstallationRequest' - ).callsFake(async (_, installationEntry) => { - await sleep(100); // Request would take some time - const result: RegisteredInstallationEntry = { - fid: installationEntry.fid, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: NEW_AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - } - }; - return result; - }); - generateAuthTokenRequestSpy = stub( - generateAuthTokenRequestModule, - 'generateAuthTokenRequest' - ).callsFake(async () => { - await sleep(100); // Request would take some time - const result: CompletedAuthToken = { - token: NEW_AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - }; - return result; - }); - }); - - describe('basic functionality', () => { - for (const [title, setup] of setupInstallationEntryMap.entries()) { - describe(`when ${title} in the DB`, () => { - beforeEach(() => setup(dependencies.appConfig)); - - it('resolves with an auth token', async () => { - const token = await getToken(dependencies); - expect(token).to.be.oneOf([AUTH_TOKEN, NEW_AUTH_TOKEN]); - }); - - it('saves the token in the DB', async () => { - const token = await getToken(dependencies); - const installationEntry = (await get( - dependencies.appConfig - )) as RegisteredInstallationEntry; - expect(installationEntry).not.to.be.undefined; - expect(installationEntry.registrationStatus).to.equal( - RequestStatus.COMPLETED - ); - expect(installationEntry.authToken.requestStatus).to.equal( - RequestStatus.COMPLETED - ); - expect( - (installationEntry.authToken as CompletedAuthToken).token - ).to.equal(token); - }); - - it('returns the same token on subsequent calls', async () => { - const token1 = await getToken(dependencies); - const token2 = await getToken(dependencies); - expect(token1).to.equal(token2); - }); - }); - } - }); - - describe('when there is no FID in the DB', () => { - it('gets the token by registering a new FID', async () => { - await getToken(dependencies); - expect(createInstallationRequestSpy).to.be.called; - expect(generateAuthTokenRequestSpy).not.to.be.called; - }); - - it('does not register a new FID on subsequent calls', async () => { - await getToken(dependencies); - await getToken(dependencies); - expect(createInstallationRequestSpy).to.be.calledOnce; - }); - - it('throws if the app is offline', async () => { - stub(navigator, 'onLine').value(false); - - await expect(getToken(dependencies)).to.be.rejected; - }); - }); - - describe('when there is a FID in the DB, but no auth token', () => { - let installationEntry: RegisteredInstallationEntry; - - beforeEach(async () => { - installationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - requestStatus: RequestStatus.NOT_STARTED - } - }; - await set(dependencies.appConfig, installationEntry); - }); - - it('gets the token by calling generateAuthToken', async () => { - await getToken(dependencies); - expect(generateAuthTokenRequestSpy).to.be.called; - expect(createInstallationRequestSpy).not.to.be.called; - }); - - it('does not call generateAuthToken twice on subsequent calls', async () => { - await getToken(dependencies); - await getToken(dependencies); - expect(generateAuthTokenRequestSpy).to.be.calledOnce; - }); - - it('does not call generateAuthToken twice on simultaneous calls', async () => { - await Promise.all([getToken(dependencies), getToken(dependencies)]); - expect(generateAuthTokenRequestSpy).to.be.calledOnce; - }); - - it('throws if the app is offline', async () => { - stub(navigator, 'onLine').value(false); - - await expect(getToken(dependencies)).to.be.rejected; - }); - - describe('and the server returns an error', () => { - it('removes the FID from the DB if the server returns a 401 response', async () => { - generateAuthTokenRequestSpy.callsFake(async () => { - throw ERROR_FACTORY.create(ErrorCode.REQUEST_FAILED, { - requestName: 'Generate Auth Token', - serverCode: 401, - serverStatus: 'UNAUTHENTICATED', - serverMessage: 'Invalid Authentication.' - }); - }); - - await expect(getToken(dependencies)).to.be.rejected; - await expect(get(dependencies.appConfig)).to.eventually.be.undefined; - }); - - it('removes the FID from the DB if the server returns a 404 response', async () => { - generateAuthTokenRequestSpy.callsFake(async () => { - throw ERROR_FACTORY.create(ErrorCode.REQUEST_FAILED, { - requestName: 'Generate Auth Token', - serverCode: 404, - serverStatus: 'NOT_FOUND', - serverMessage: 'FID not found.' - }); - }); - - await expect(getToken(dependencies)).to.be.rejected; - await expect(get(dependencies.appConfig)).to.eventually.be.undefined; - }); - - it('does not remove the FID from the DB if the server returns any other response', async () => { - generateAuthTokenRequestSpy.callsFake(async () => { - throw ERROR_FACTORY.create(ErrorCode.REQUEST_FAILED, { - requestName: 'Generate Auth Token', - serverCode: 500, - serverStatus: 'INTERNAL', - serverMessage: 'Internal server error.' - }); - }); - - await expect(getToken(dependencies)).to.be.rejected; - await expect(get(dependencies.appConfig)).to.eventually.deep.equal( - installationEntry - ); - }); - }); - }); - - describe('when there is a registered auth token in the DB', () => { - beforeEach(async () => { - const installationEntry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - } - }; - await set(dependencies.appConfig, installationEntry); - }); - - it('does not call any server APIs', async () => { - await getToken(dependencies); - expect(createInstallationRequestSpy).not.to.be.called; - expect(generateAuthTokenRequestSpy).not.to.be.called; - }); - - it('refreshes the token if forceRefresh is true', async () => { - const token = await getToken(dependencies, true); - expect(token).to.equal(NEW_AUTH_TOKEN); - expect(generateAuthTokenRequestSpy).to.be.called; - }); - - it('works even if the app is offline', async () => { - stub(navigator, 'onLine').value(false); - - const token = await getToken(dependencies); - expect(token).to.equal(AUTH_TOKEN); - }); - - it('throws if the app is offline and forceRefresh is true', async () => { - stub(navigator, 'onLine').value(false); - - await expect(getToken(dependencies, true)).to.be.rejected; - }); - }); - - describe('when there is an auth token that is about to expire in the DB', () => { - let clock: SinonFakeTimers; - - beforeEach(async () => { - clock = useFakeTimers({ shouldAdvanceTime: true }); - const installationEntry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: - // Expires in ten minutes - Date.now() - ONE_WEEK_MS + TOKEN_EXPIRATION_BUFFER + 10 * 60 * 1000 - } - }; - await set(dependencies.appConfig, installationEntry); - }); - - it('returns a different token after expiration', async () => { - const token1 = await getToken(dependencies); - expect(token1).to.equal(AUTH_TOKEN); - - // Wait 30 minutes. - clock.tick('30:00'); - - const token2 = await getToken(dependencies); - await expect(token2).to.equal(NEW_AUTH_TOKEN); - await expect(token2).not.to.equal(token1); - }); - }); - - describe('when there is an expired auth token in the DB', () => { - beforeEach(async () => { - const installationEntry: RegisteredInstallationEntry = { - fid: FID, - registrationStatus: RequestStatus.COMPLETED, - refreshToken: 'refreshToken', - authToken: { - token: AUTH_TOKEN, - expiresIn: ONE_WEEK_MS, - requestStatus: RequestStatus.COMPLETED, - creationTime: Date.now() - 2 * ONE_WEEK_MS - } - }; - await set(dependencies.appConfig, installationEntry); - }); - - it('returns a different token', async () => { - const token = await getToken(dependencies); - expect(token).to.equal(NEW_AUTH_TOKEN); - expect(generateAuthTokenRequestSpy).to.be.called; - }); - - it('throws if the app is offline', async () => { - stub(navigator, 'onLine').value(false); - - await expect(getToken(dependencies)).to.be.rejected; - }); - }); -}); diff --git a/packages/installations/src/functions/get-token.ts b/packages/installations/src/functions/get-token.ts deleted file mode 100644 index a747626f476..00000000000 --- a/packages/installations/src/functions/get-token.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getInstallationEntry } from '../helpers/get-installation-entry'; -import { refreshAuthToken } from '../helpers/refresh-auth-token'; -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; - -export async function getToken( - dependencies: FirebaseDependencies, - forceRefresh = false -): Promise { - await completeInstallationRegistration(dependencies.appConfig); - - // At this point we either have a Registered Installation in the DB, or we've - // already thrown an error. - const authToken = await refreshAuthToken(dependencies, forceRefresh); - return authToken.token; -} - -async function completeInstallationRegistration( - appConfig: AppConfig -): Promise { - const { registrationPromise } = await getInstallationEntry(appConfig); - - if (registrationPromise) { - // A createInstallation request is in progress. Wait until it finishes. - await registrationPromise; - } -} diff --git a/packages/installations/src/functions/index.ts b/packages/installations/src/functions/index.ts deleted file mode 100644 index 7952e267a45..00000000000 --- a/packages/installations/src/functions/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './get-id'; -export * from './get-token'; -export * from './delete-installation'; -export * from './on-id-change'; diff --git a/packages/installations/src/functions/on-id-change.test.ts b/packages/installations/src/functions/on-id-change.test.ts deleted file mode 100644 index 89175b07eb3..00000000000 --- a/packages/installations/src/functions/on-id-change.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from 'chai'; -import { stub } from 'sinon'; -import '../testing/setup'; -import { onIdChange } from './on-id-change'; -import * as FidChangedModule from '../helpers/fid-changed'; -import { getFakeDependencies } from '../testing/fake-generators'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; - -describe('onIdChange', () => { - let dependencies: FirebaseDependencies; - - beforeEach(() => { - dependencies = getFakeDependencies(); - stub(FidChangedModule); - }); - - it('calls addCallback with the given callback and app key when called', () => { - const callback = stub(); - onIdChange(dependencies, callback); - expect(FidChangedModule.addCallback).to.have.been.calledOnceWith( - dependencies.appConfig, - callback - ); - }); - - it('calls removeCallback with the given callback and app key when unsubscribe is called', () => { - const callback = stub(); - const unsubscribe = onIdChange(dependencies, callback); - unsubscribe(); - expect(FidChangedModule.removeCallback).to.have.been.calledOnceWith( - dependencies.appConfig, - callback - ); - }); -}); diff --git a/packages/installations/src/functions/on-id-change.ts b/packages/installations/src/functions/on-id-change.ts deleted file mode 100644 index c417b005e43..00000000000 --- a/packages/installations/src/functions/on-id-change.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { addCallback, removeCallback } from '../helpers/fid-changed'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; - -export type IdChangeCallbackFn = (installationId: string) => void; -export type IdChangeUnsubscribeFn = () => void; - -/** - * Sets a new callback that will get called when Installation ID changes. - * Returns an unsubscribe function that will remove the callback when called. - */ -export function onIdChange( - { appConfig }: FirebaseDependencies, - callback: IdChangeCallbackFn -): IdChangeUnsubscribeFn { - addCallback(appConfig, callback); - - return () => { - removeCallback(appConfig, callback); - }; -} diff --git a/packages/installations/src/helpers/extract-app-config.test.ts b/packages/installations/src/helpers/extract-app-config.test.ts index c7dd3b0d5c2..06a7ec2dcb6 100644 --- a/packages/installations/src/helpers/extract-app-config.test.ts +++ b/packages/installations/src/helpers/extract-app-config.test.ts @@ -17,7 +17,7 @@ import { FirebaseError } from '@firebase/util'; import { expect } from 'chai'; -import { AppConfig } from '../interfaces/app-config'; +import { AppConfig } from '../interfaces/installation-impl'; import { getFakeApp } from '../testing/fake-generators'; import '../testing/setup'; import { extractAppConfig } from './extract-app-config'; @@ -38,13 +38,11 @@ describe('extractAppConfig', () => { expect(() => extractAppConfig(undefined as any)).to.throw(FirebaseError); let firebaseApp = getFakeApp(); - // @ts-expect-error - delete firebaseApp.name; + delete (firebaseApp as any).name; expect(() => extractAppConfig(firebaseApp)).to.throw(FirebaseError); firebaseApp = getFakeApp(); - // @ts-expect-error - delete firebaseApp.options; + delete (firebaseApp as any).options; expect(() => extractAppConfig(firebaseApp)).to.throw(FirebaseError); firebaseApp = getFakeApp(); diff --git a/packages/installations/src/helpers/extract-app-config.ts b/packages/installations/src/helpers/extract-app-config.ts index 51f1bad2e71..5b018aa8c7b 100644 --- a/packages/installations/src/helpers/extract-app-config.ts +++ b/packages/installations/src/helpers/extract-app-config.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { FirebaseApp, FirebaseOptions } from '@firebase/app-types'; +import { FirebaseApp, FirebaseOptions } from '@firebase/app'; import { FirebaseError } from '@firebase/util'; -import { AppConfig } from '../interfaces/app-config'; +import { AppConfig } from '../interfaces/installation-impl'; import { ERROR_FACTORY, ErrorCode } from '../util/errors'; export function extractAppConfig(app: FirebaseApp): AppConfig { diff --git a/packages/installations/src/helpers/fid-changed.test.ts b/packages/installations/src/helpers/fid-changed.test.ts index 99a9e5a462d..458bc1d097d 100644 --- a/packages/installations/src/helpers/fid-changed.test.ts +++ b/packages/installations/src/helpers/fid-changed.test.ts @@ -18,7 +18,7 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import '../testing/setup'; -import { AppConfig } from '../interfaces/app-config'; +import { AppConfig } from '../interfaces/installation-impl'; import { fidChanged, addCallback, diff --git a/packages/installations/src/helpers/fid-changed.ts b/packages/installations/src/helpers/fid-changed.ts index 044950085a6..63f04d75cea 100644 --- a/packages/installations/src/helpers/fid-changed.ts +++ b/packages/installations/src/helpers/fid-changed.ts @@ -16,8 +16,8 @@ */ import { getKey } from '../util/get-key'; -import { AppConfig } from '../interfaces/app-config'; -import { IdChangeCallbackFn } from '../functions'; +import { AppConfig } from '../interfaces/installation-impl'; +import { IdChangeCallbackFn } from '../api'; const fidChangeCallbacks: Map> = new Map(); diff --git a/packages/installations/src/helpers/get-installation-entry.test.ts b/packages/installations/src/helpers/get-installation-entry.test.ts index e1b830bd0c6..b19ec6ea037 100644 --- a/packages/installations/src/helpers/get-installation-entry.test.ts +++ b/packages/installations/src/helpers/get-installation-entry.test.ts @@ -17,8 +17,8 @@ import { AssertionError, expect } from 'chai'; import { SinonFakeTimers, SinonStub, stub, useFakeTimers } from 'sinon'; -import * as createInstallationRequestModule from '../api/create-installation-request'; -import { AppConfig } from '../interfaces/app-config'; +import * as createInstallationRequestModule from '../functions/create-installation-request'; +import { AppConfig } from '../interfaces/installation-impl'; import { InProgressInstallationEntry, RegisteredInstallationEntry, diff --git a/packages/installations/src/helpers/get-installation-entry.ts b/packages/installations/src/helpers/get-installation-entry.ts index 43cb443c533..6b57563eb80 100644 --- a/packages/installations/src/helpers/get-installation-entry.ts +++ b/packages/installations/src/helpers/get-installation-entry.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { createInstallationRequest } from '../api/create-installation-request'; -import { AppConfig } from '../interfaces/app-config'; +import { createInstallationRequest } from '../functions/create-installation-request'; +import { AppConfig } from '../interfaces/installation-impl'; import { InProgressInstallationEntry, InstallationEntry, diff --git a/packages/installations/src/helpers/idb-manager.test.ts b/packages/installations/src/helpers/idb-manager.test.ts index 3dbaa9f5091..db7eaca58f9 100644 --- a/packages/installations/src/helpers/idb-manager.test.ts +++ b/packages/installations/src/helpers/idb-manager.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; import { stub } from 'sinon'; -import { AppConfig } from '../interfaces/app-config'; +import { AppConfig } from '../interfaces/installation-impl'; import { InstallationEntry, RequestStatus diff --git a/packages/installations/src/helpers/idb-manager.ts b/packages/installations/src/helpers/idb-manager.ts index 8bc464fd115..bc30563fa06 100644 --- a/packages/installations/src/helpers/idb-manager.ts +++ b/packages/installations/src/helpers/idb-manager.ts @@ -16,7 +16,7 @@ */ import { DB, openDb } from 'idb'; -import { AppConfig } from '../interfaces/app-config'; +import { AppConfig } from '../interfaces/installation-impl'; import { InstallationEntry } from '../interfaces/installation-entry'; import { getKey } from '../util/get-key'; import { fidChanged } from './fid-changed'; diff --git a/packages/installations/src/helpers/refresh-auth-token.test.ts b/packages/installations/src/helpers/refresh-auth-token.test.ts index c3a66782292..3bdd859a6b0 100644 --- a/packages/installations/src/helpers/refresh-auth-token.test.ts +++ b/packages/installations/src/helpers/refresh-auth-token.test.ts @@ -17,20 +17,20 @@ import { expect } from 'chai'; import { SinonFakeTimers, SinonStub, stub, useFakeTimers } from 'sinon'; -import * as generateAuthTokenRequestModule from '../api/generate-auth-token-request'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; +import * as generateAuthTokenRequestModule from '../functions/generate-auth-token-request'; import { CompletedAuthToken, RegisteredInstallationEntry, RequestStatus, UnregisteredInstallationEntry } from '../interfaces/installation-entry'; -import { getFakeDependencies } from '../testing/fake-generators'; +import { getFakeInstallations } from '../testing/fake-generators'; import '../testing/setup'; import { TOKEN_EXPIRATION_BUFFER } from '../util/constants'; import { sleep } from '../util/sleep'; import { get, set } from './idb-manager'; import { refreshAuthToken } from './refresh-auth-token'; +import { FirebaseInstallationsImpl } from '../interfaces/installation-impl'; const FID = 'carry-the-blessed-home'; const AUTH_TOKEN = 'authTokenFromServer'; @@ -38,14 +38,14 @@ const DB_AUTH_TOKEN = 'authTokenFromDB'; const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000; describe('refreshAuthToken', () => { - let dependencies: FirebaseDependencies; + let installations: FirebaseInstallationsImpl; let generateAuthTokenRequestSpy: SinonStub< - [FirebaseDependencies, RegisteredInstallationEntry], + [FirebaseInstallationsImpl, RegisteredInstallationEntry], Promise >; beforeEach(() => { - dependencies = getFakeDependencies(); + installations = getFakeInstallations(); generateAuthTokenRequestSpy = stub( generateAuthTokenRequestModule, @@ -63,7 +63,7 @@ describe('refreshAuthToken', () => { }); it('throws when there is no installation in the DB', async () => { - await expect(refreshAuthToken(dependencies)).to.be.rejected; + await expect(refreshAuthToken(installations)).to.be.rejected; }); it('throws when there is an unregistered installation in the db', async () => { @@ -71,9 +71,9 @@ describe('refreshAuthToken', () => { fid: FID, registrationStatus: RequestStatus.NOT_STARTED }; - await set(dependencies.appConfig, installationEntry); + await set(installations.appConfig, installationEntry); - await expect(refreshAuthToken(dependencies)).to.be.rejected; + await expect(refreshAuthToken(installations)).to.be.rejected; }); describe('when there is a valid auth token in the DB', () => { @@ -89,23 +89,23 @@ describe('refreshAuthToken', () => { creationTime: Date.now() } }; - await set(dependencies.appConfig, installationEntry); + await set(installations.appConfig, installationEntry); }); it('returns the token from the DB', async () => { - const { token } = await refreshAuthToken(dependencies); + const { token } = await refreshAuthToken(installations); expect(token).to.equal(AUTH_TOKEN); }); it('does not call any server APIs', async () => { - await refreshAuthToken(dependencies); + await refreshAuthToken(installations); expect(generateAuthTokenRequestSpy).not.to.be.called; }); it('works even if the app is offline', async () => { stub(navigator, 'onLine').value(false); - const { token } = await refreshAuthToken(dependencies); + const { token } = await refreshAuthToken(installations); expect(token).to.equal(AUTH_TOKEN); }); }); @@ -129,17 +129,17 @@ describe('refreshAuthToken', () => { Date.now() - ONE_WEEK_MS + TOKEN_EXPIRATION_BUFFER + 10 * 60 * 1000 } }; - await set(dependencies.appConfig, installationEntry); + await set(installations.appConfig, installationEntry); }); it('returns a different token after expiration', async () => { - const token1 = await refreshAuthToken(dependencies); + const token1 = await refreshAuthToken(installations); expect(token1.token).to.equal(DB_AUTH_TOKEN); // Wait 30 minutes. clock.tick('30:00'); - const token2 = await refreshAuthToken(dependencies); + const token2 = await refreshAuthToken(installations); await expect(token2.token).to.equal(AUTH_TOKEN); await expect(token2.token).not.to.equal(DB_AUTH_TOKEN); expect(generateAuthTokenRequestSpy).to.be.calledOnce; @@ -159,25 +159,25 @@ describe('refreshAuthToken', () => { creationTime: Date.now() - 2 * ONE_WEEK_MS } }; - await set(dependencies.appConfig, installationEntry); + await set(installations.appConfig, installationEntry); }); it('does not call generateAuthToken twice on subsequent calls', async () => { - await refreshAuthToken(dependencies); - await refreshAuthToken(dependencies); + await refreshAuthToken(installations); + await refreshAuthToken(installations); expect(generateAuthTokenRequestSpy).to.be.calledOnce; }); it('does not call generateAuthToken twice on simultaneous calls', async () => { await Promise.all([ - refreshAuthToken(dependencies), - refreshAuthToken(dependencies) + refreshAuthToken(installations), + refreshAuthToken(installations) ]); expect(generateAuthTokenRequestSpy).to.be.calledOnce; }); it('returns a new token', async () => { - const { token } = await refreshAuthToken(dependencies); + const { token } = await refreshAuthToken(installations); await expect(token).to.equal(AUTH_TOKEN); await expect(token).not.to.equal(DB_AUTH_TOKEN); expect(generateAuthTokenRequestSpy).to.be.calledOnce; @@ -186,14 +186,14 @@ describe('refreshAuthToken', () => { it('throws if the app is offline', async () => { stub(navigator, 'onLine').value(false); - await expect(refreshAuthToken(dependencies)).to.be.rejected; + await expect(refreshAuthToken(installations)).to.be.rejected; }); it('saves the new token in the DB', async () => { - const { token } = await refreshAuthToken(dependencies); + const { token } = await refreshAuthToken(installations); const installationEntry = (await get( - dependencies.appConfig + installations.appConfig )) as RegisteredInstallationEntry; expect(installationEntry).not.to.be.undefined; expect(installationEntry.registrationStatus).to.equal( diff --git a/packages/installations/src/helpers/refresh-auth-token.ts b/packages/installations/src/helpers/refresh-auth-token.ts index 8c763f9820d..ac2a0ffc02d 100644 --- a/packages/installations/src/helpers/refresh-auth-token.ts +++ b/packages/installations/src/helpers/refresh-auth-token.ts @@ -15,9 +15,11 @@ * limitations under the License. */ -import { generateAuthTokenRequest } from '../api/generate-auth-token-request'; -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; +import { generateAuthTokenRequest } from '../functions/generate-auth-token-request'; +import { + AppConfig, + FirebaseInstallationsImpl +} from '../interfaces/installation-impl'; import { AuthToken, CompletedAuthToken, @@ -38,11 +40,11 @@ import { remove, set, update } from './idb-manager'; * Should only be called if the Firebase Installation is registered. */ export async function refreshAuthToken( - dependencies: FirebaseDependencies, + installations: FirebaseInstallationsImpl, forceRefresh = false ): Promise { let tokenPromise: Promise | undefined; - const entry = await update(dependencies.appConfig, oldEntry => { + const entry = await update(installations.appConfig, oldEntry => { if (!isEntryRegistered(oldEntry)) { throw ERROR_FACTORY.create(ErrorCode.NOT_REGISTERED); } @@ -53,7 +55,7 @@ export async function refreshAuthToken( return oldEntry; } else if (oldAuthToken.requestStatus === RequestStatus.IN_PROGRESS) { // There already is a token request in progress. - tokenPromise = waitUntilAuthTokenRequest(dependencies, forceRefresh); + tokenPromise = waitUntilAuthTokenRequest(installations, forceRefresh); return oldEntry; } else { // No token or token expired. @@ -62,7 +64,7 @@ export async function refreshAuthToken( } const inProgressEntry = makeAuthTokenRequestInProgressEntry(oldEntry); - tokenPromise = fetchAuthTokenFromServer(dependencies, inProgressEntry); + tokenPromise = fetchAuthTokenFromServer(installations, inProgressEntry); return inProgressEntry; } }); @@ -80,25 +82,25 @@ export async function refreshAuthToken( * tries once in this thread as well. */ async function waitUntilAuthTokenRequest( - dependencies: FirebaseDependencies, + installations: FirebaseInstallationsImpl, forceRefresh: boolean ): Promise { // Unfortunately, there is no way of reliably observing when a value in // IndexedDB changes (yet, see https://github.com/WICG/indexed-db-observers), // so we need to poll. - let entry = await updateAuthTokenRequest(dependencies.appConfig); + let entry = await updateAuthTokenRequest(installations.appConfig); while (entry.authToken.requestStatus === RequestStatus.IN_PROGRESS) { // generateAuthToken still in progress. await sleep(100); - entry = await updateAuthTokenRequest(dependencies.appConfig); + entry = await updateAuthTokenRequest(installations.appConfig); } const authToken = entry.authToken; if (authToken.requestStatus === RequestStatus.NOT_STARTED) { // The request timed out or failed in a different call. Try again. - return refreshAuthToken(dependencies, forceRefresh); + return refreshAuthToken(installations, forceRefresh); } else { return authToken; } @@ -133,19 +135,19 @@ function updateAuthTokenRequest( } async function fetchAuthTokenFromServer( - dependencies: FirebaseDependencies, + installations: FirebaseInstallationsImpl, installationEntry: RegisteredInstallationEntry ): Promise { try { const authToken = await generateAuthTokenRequest( - dependencies, + installations, installationEntry ); const updatedInstallationEntry: RegisteredInstallationEntry = { ...installationEntry, authToken }; - await set(dependencies.appConfig, updatedInstallationEntry); + await set(installations.appConfig, updatedInstallationEntry); return authToken; } catch (e) { if ( @@ -154,13 +156,13 @@ async function fetchAuthTokenFromServer( ) { // Server returned a "FID not found" or a "Invalid authentication" error. // Generate a new ID next time. - await remove(dependencies.appConfig); + await remove(installations.appConfig); } else { const updatedInstallationEntry: RegisteredInstallationEntry = { ...installationEntry, authToken: { requestStatus: RequestStatus.NOT_STARTED } }; - await set(dependencies.appConfig, updatedInstallationEntry); + await set(installations.appConfig, updatedInstallationEntry); } throw e; } diff --git a/packages/installations/src/index.ts b/packages/installations/src/index.ts index a3d9257bb49..2d7f4b1b77b 100644 --- a/packages/installations/src/index.ts +++ b/packages/installations/src/index.ts @@ -1,3 +1,9 @@ +/** + * Firebase Installations + * + * @packageDocumentation + */ + /** * @license * Copyright 2019 Google LLC @@ -15,71 +21,12 @@ * limitations under the License. */ -import firebase from '@firebase/app'; -import { - _FirebaseNamespace, - FirebaseService -} from '@firebase/app-types/private'; -import { Component, ComponentType } from '@firebase/component'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import { - deleteInstallation, - getId, - getToken, - IdChangeCallbackFn, - IdChangeUnsubscribeFn, - onIdChange -} from './functions'; -import { extractAppConfig } from './helpers/extract-app-config'; -import { FirebaseDependencies } from './interfaces/firebase-dependencies'; - +import { registerInstallations } from './functions/config'; +import { registerVersion } from '@firebase/app'; import { name, version } from '../package.json'; -export function registerInstallations(instance: _FirebaseNamespace): void { - const installationsName = 'installations'; - - instance.INTERNAL.registerComponent( - new Component( - installationsName, - container => { - const app = container.getProvider('app').getImmediate(); +export * from './api'; +export * from './interfaces/public-types'; - // Throws if app isn't configured properly. - const appConfig = extractAppConfig(app); - const platformLoggerProvider = container.getProvider('platform-logger'); - const dependencies: FirebaseDependencies = { - appConfig, - platformLoggerProvider - }; - - const installations: FirebaseInstallations & FirebaseService = { - app, - getId: () => getId(dependencies), - getToken: (forceRefresh?: boolean) => - getToken(dependencies, forceRefresh), - delete: () => deleteInstallation(dependencies), - onIdChange: (callback: IdChangeCallbackFn): IdChangeUnsubscribeFn => - onIdChange(dependencies, callback) - }; - return installations; - }, - ComponentType.PUBLIC - ) - ); - - instance.registerVersion(name, version); -} - -registerInstallations(firebase as _FirebaseNamespace); - -/** - * Define extension behavior of `registerInstallations` - */ -declare module '@firebase/app-types' { - interface FirebaseNamespace { - installations(app?: FirebaseApp): FirebaseInstallations; - } - interface FirebaseApp { - installations(): FirebaseInstallations; - } -} +registerInstallations(); +registerVersion(name, version); diff --git a/packages/installations/src/interfaces/app-config.ts b/packages/installations/src/interfaces/app-config.ts deleted file mode 100644 index 007f14f1db4..00000000000 --- a/packages/installations/src/interfaces/app-config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export interface AppConfig { - readonly appName: string; - readonly projectId: string; - readonly apiKey: string; - readonly appId: string; -} diff --git a/packages/installations/src/interfaces/firebase-dependencies.ts b/packages/installations/src/interfaces/firebase-dependencies.ts deleted file mode 100644 index 3dd3c2eb03b..00000000000 --- a/packages/installations/src/interfaces/firebase-dependencies.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Provider } from '@firebase/component'; -import { AppConfig } from './app-config'; - -export interface FirebaseDependencies { - readonly appConfig: AppConfig; - readonly platformLoggerProvider: Provider<'platform-logger'>; -} diff --git a/packages-exp/installations-exp/src/interfaces/installation-impl.ts b/packages/installations/src/interfaces/installation-impl.ts similarity index 95% rename from packages-exp/installations-exp/src/interfaces/installation-impl.ts rename to packages/installations/src/interfaces/installation-impl.ts index 8c1a1147e94..2598275f9ad 100644 --- a/packages-exp/installations-exp/src/interfaces/installation-impl.ts +++ b/packages/installations/src/interfaces/installation-impl.ts @@ -16,7 +16,7 @@ */ import { Provider } from '@firebase/component'; -import { _FirebaseService } from '@firebase/app-exp'; +import { _FirebaseService } from '@firebase/app'; import { Installations } from '../interfaces/public-types'; export interface FirebaseInstallationsImpl diff --git a/packages-exp/installations-exp/src/interfaces/public-types.ts b/packages/installations/src/interfaces/public-types.ts similarity index 80% rename from packages-exp/installations-exp/src/interfaces/public-types.ts rename to packages/installations/src/interfaces/public-types.ts index d37aa69e6bb..c619127f472 100644 --- a/packages-exp/installations-exp/src/interfaces/public-types.ts +++ b/packages/installations/src/interfaces/public-types.ts @@ -15,12 +15,19 @@ * limitations under the License. */ +import { FirebaseApp } from '@firebase/app'; + /** * Public interface of the Firebase Installations SDK. * * @public */ -export interface Installations {} +export interface Installations { + /** + * The {@link @firebase/app#FirebaseApp} this `Installations` instance is associated with. + */ + app: FirebaseApp; +} /** * An interface for Firebase internal SDKs use only. @@ -43,7 +50,7 @@ export interface _FirebaseInstallationsInternal { declare module '@firebase/component' { interface NameServiceMapping { - 'installations-exp': Installations; - 'installations-exp-internal': _FirebaseInstallationsInternal; + 'installations': Installations; + 'installations-internal': _FirebaseInstallationsInternal; } } diff --git a/packages/installations/src/testing/fake-generators.ts b/packages/installations/src/testing/fake-generators.ts index c6a0fbec63e..419e0151e43 100644 --- a/packages/installations/src/testing/fake-generators.ts +++ b/packages/installations/src/testing/fake-generators.ts @@ -15,15 +15,17 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseApp } from '@firebase/app'; import { Component, ComponentContainer, ComponentType } from '@firebase/component'; import { extractAppConfig } from '../helpers/extract-app-config'; -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseDependencies } from '../interfaces/firebase-dependencies'; +import { + FirebaseInstallationsImpl, + AppConfig +} from '../interfaces/installation-impl'; export function getFakeApp(): FirebaseApp { return { @@ -37,11 +39,7 @@ export function getFakeApp(): FirebaseApp { storageBucket: 'storageBucket', appId: '1:777777777777:web:d93b5ca1475efe57' }, - automaticDataCollectionEnabled: true, - delete: async () => {}, - // This won't be used in tests. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - installations: null as any + automaticDataCollectionEnabled: true }; } @@ -51,7 +49,7 @@ export function getFakeAppConfig( return { ...extractAppConfig(getFakeApp()), ...customValues }; } -export function getFakeDependencies(): FirebaseDependencies { +export function getFakeInstallations(): FirebaseInstallationsImpl { const container = new ComponentContainer('test'); container.addComponent( new Component( @@ -62,7 +60,11 @@ export function getFakeDependencies(): FirebaseDependencies { ); return { + app: getFakeApp(), appConfig: getFakeAppConfig(), - platformLoggerProvider: container.getProvider('platform-logger') + platformLoggerProvider: container.getProvider('platform-logger'), + _delete: () => { + return Promise.resolve(); + } }; } diff --git a/packages/installations/src/util/get-key.ts b/packages/installations/src/util/get-key.ts index 84c1ecd6239..272d342d366 100644 --- a/packages/installations/src/util/get-key.ts +++ b/packages/installations/src/util/get-key.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { AppConfig } from '../interfaces/app-config'; +import { AppConfig } from '../interfaces/installation-impl'; /** Returns a string key that can be used to identify the app. */ export function getKey(appConfig: AppConfig): string { diff --git a/packages/installations/src/util/sleep.test.ts b/packages/installations/src/util/sleep.test.ts index 86ae066fb31..6dfc4b328ee 100644 --- a/packages/installations/src/util/sleep.test.ts +++ b/packages/installations/src/util/sleep.test.ts @@ -17,6 +17,7 @@ import { expect } from 'chai'; import { SinonFakeTimers, useFakeTimers } from 'sinon'; +import '../testing/setup'; import { sleep } from './sleep'; describe('sleep', () => { diff --git a/packages/installations/src/util/sleep.ts b/packages/installations/src/util/sleep.ts index e9a902de343..2bd1eb9283b 100644 --- a/packages/installations/src/util/sleep.ts +++ b/packages/installations/src/util/sleep.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2020 Google LLC + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/installations/test-app/index.html b/packages/installations/test-app/index.html index 4eb8d35e28a..f5e2958cea0 100644 --- a/packages/installations/test-app/index.html +++ b/packages/installations/test-app/index.html @@ -18,15 +18,15 @@

- +

- +

- +

diff --git a/packages/installations/test-app/index.js b/packages/installations/test-app/index.js index f534f411aac..d0101e3114e 100644 --- a/packages/installations/test-app/index.js +++ b/packages/installations/test-app/index.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,7 +103,7 @@ function getApp() { const appId = getInputValue('appId'); return { name: appName, - options: { projectId, apiKey, appId } + appConfig: { appName, projectId, apiKey, appId } }; } diff --git a/packages/installations/test-app/rollup.config.js b/packages/installations/test-app/rollup.config.js index 4a3f71fa295..6f6d3a3eae7 100644 --- a/packages/installations/test-app/rollup.config.js +++ b/packages/installations/test-app/rollup.config.js @@ -27,7 +27,7 @@ import typescript from 'typescript'; */ export default [ { - input: 'src/functions/index.ts', + input: 'src/api/index.ts', output: { name: 'FirebaseInstallations', file: 'test-app/sdk.js', diff --git a/packages/installations/tsconfig.json b/packages/installations/tsconfig.json index b957e47de5d..420eda97a1d 100644 --- a/packages/installations/tsconfig.json +++ b/packages/installations/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../config/tsconfig.base.json", "compilerOptions": { + "outDir": "dist", "downlevelIteration": true, "resolveJsonModule": true, "noUnusedLocals": true, @@ -8,5 +9,6 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["dist/**/*"] } diff --git a/packages-exp/messaging-compat/.eslintrc.js b/packages/messaging-compat/.eslintrc.js similarity index 100% rename from packages-exp/messaging-compat/.eslintrc.js rename to packages/messaging-compat/.eslintrc.js diff --git a/packages-exp/messaging-compat/README.md b/packages/messaging-compat/README.md similarity index 100% rename from packages-exp/messaging-compat/README.md rename to packages/messaging-compat/README.md diff --git a/packages-exp/messaging-compat/karma.conf.js b/packages/messaging-compat/karma.conf.js similarity index 100% rename from packages-exp/messaging-compat/karma.conf.js rename to packages/messaging-compat/karma.conf.js diff --git a/packages-exp/messaging-compat/package.json b/packages/messaging-compat/package.json similarity index 81% rename from packages-exp/messaging-compat/package.json rename to packages/messaging-compat/package.json index a9b7767d675..0782ba70e5b 100644 --- a/packages-exp/messaging-compat/package.json +++ b/packages/messaging-compat/package.json @@ -3,7 +3,6 @@ "version": "0.0.900", "license": "Apache-2.0", "description": "", - "private": true, "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", "browser": "dist/index.esm2017.js", @@ -18,22 +17,21 @@ "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "build": "rollup -c", "build:deps": "lerna run --scope @firebase/'messaging-compat' --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js && yarn add-compat-overloads", + "build:release": "yarn build && yarn add-compat-overloads", "dev": "rollup -c -w", "test": "run-p test:karma", "test:ci": "node ../../scripts/run_tests_in_ci.js", "test:karma": "karma start --single-run", "test:debug": "karma start --browsers=Chrome --auto-watch", "type-check": "tsc --noEmit", - "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../messaging-exp/dist/index-public.d.ts -o dist/src/index.d.ts -a -r FirebaseMessaging:MessagingCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/messaging" + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../messaging/dist/index-public.d.ts -o dist/src/index.d.ts -a -r FirebaseMessaging:MessagingCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/messaging" }, "peerDependencies": { "@firebase/app-compat": "0.x" }, "dependencies": { - "@firebase/messaging-exp": "0.0.900", + "@firebase/messaging": "0.8.0", "@firebase/component": "0.5.6", - "@firebase/installations-exp": "0.0.900", "@firebase/util": "1.3.0", "tslib": "^2.1.0" }, diff --git a/packages-exp/messaging-compat/rollup.config.js b/packages/messaging-compat/rollup.config.js similarity index 100% rename from packages-exp/messaging-compat/rollup.config.js rename to packages/messaging-compat/rollup.config.js diff --git a/packages-exp/messaging-compat/src/index.ts b/packages/messaging-compat/src/index.ts similarity index 100% rename from packages-exp/messaging-compat/src/index.ts rename to packages/messaging-compat/src/index.ts diff --git a/packages-exp/messaging-compat/src/messaging-compat.ts b/packages/messaging-compat/src/messaging-compat.ts similarity index 96% rename from packages-exp/messaging-compat/src/messaging-compat.ts rename to packages/messaging-compat/src/messaging-compat.ts index d3f863a4292..2910d61b341 100644 --- a/packages-exp/messaging-compat/src/messaging-compat.ts +++ b/packages/messaging-compat/src/messaging-compat.ts @@ -25,10 +25,10 @@ import { deleteToken, getToken, onMessage -} from '@firebase/messaging-exp'; +} from '@firebase/messaging'; import { NextFn, Observer, Unsubscribe } from '@firebase/util'; -import { onBackgroundMessage } from '@firebase/messaging-exp/sw'; +import { onBackgroundMessage } from '@firebase/messaging/sw'; export interface MessagingCompat { getToken(options?: { diff --git a/packages-exp/messaging-compat/src/registerMessagingCompat.ts b/packages/messaging-compat/src/registerMessagingCompat.ts similarity index 90% rename from packages-exp/messaging-compat/src/registerMessagingCompat.ts rename to packages/messaging-compat/src/registerMessagingCompat.ts index 0dbd5d1ceef..2783029c60b 100644 --- a/packages-exp/messaging-compat/src/registerMessagingCompat.ts +++ b/packages/messaging-compat/src/registerMessagingCompat.ts @@ -34,17 +34,17 @@ declare module '@firebase/component' { const messagingCompatFactory: InstanceFactory<'messaging-compat'> = ( container: ComponentContainer ) => { - if (!!navigator) { - // in window + if (self && 'ServiceWorkerGlobalScope' in self) { + // in sw return new MessagingCompatImpl( container.getProvider('app-compat').getImmediate(), - container.getProvider('messaging-exp').getImmediate() + container.getProvider('messaging-sw').getImmediate() ); } else { - // in sw + // in window return new MessagingCompatImpl( container.getProvider('app-compat').getImmediate(), - container.getProvider('messaging-sw-exp').getImmediate() + container.getProvider('messaging').getImmediate() ); } }; diff --git a/packages-exp/messaging-compat/test/fakes.ts b/packages/messaging-compat/test/fakes.ts similarity index 95% rename from packages-exp/messaging-compat/test/fakes.ts rename to packages/messaging-compat/test/fakes.ts index 92569b1a3c5..6b8cdc555dd 100644 --- a/packages-exp/messaging-compat/test/fakes.ts +++ b/packages/messaging-compat/test/fakes.ts @@ -16,7 +16,7 @@ */ import { FirebaseApp } from '@firebase/app-compat'; -import { Messaging } from '@firebase/messaging-exp'; +import { Messaging } from '@firebase/messaging'; export function getFakeApp(): FirebaseApp { return { diff --git a/packages-exp/messaging-compat/test/messaging-compat.test.ts b/packages/messaging-compat/test/messaging-compat.test.ts similarity index 93% rename from packages-exp/messaging-compat/test/messaging-compat.test.ts rename to packages/messaging-compat/test/messaging-compat.test.ts index 9c76fe1ce10..c64fc2d033c 100644 --- a/packages-exp/messaging-compat/test/messaging-compat.test.ts +++ b/packages/messaging-compat/test/messaging-compat.test.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import * as messagingModule from '@firebase/messaging-exp'; -import * as messagingModuleInSw from '@firebase/messaging-exp/sw'; +import * as messagingModule from '@firebase/messaging'; +import * as messagingModuleInSw from '@firebase/messaging/sw'; import { getFakeApp, getFakeModularMessaging } from './fakes'; diff --git a/packages-exp/messaging-compat/tsconfig.json b/packages/messaging-compat/tsconfig.json similarity index 100% rename from packages-exp/messaging-compat/tsconfig.json rename to packages/messaging-compat/tsconfig.json diff --git a/packages/messaging-types/index.d.ts b/packages/messaging-types/index.d.ts index b5fbf5fcb20..c1a30985842 100644 --- a/packages/messaging-types/index.d.ts +++ b/packages/messaging-types/index.d.ts @@ -86,10 +86,10 @@ export interface FirebaseMessaging { usePublicVapidKey(b64PublicKey: string): void; } -export type FirebaseMessagingName = 'messaging'; +export type FirebaseMessagingName = 'messaging-compat'; declare module '@firebase/component' { interface NameServiceMapping { - 'messaging': FirebaseMessaging; + 'messaging-compat': FirebaseMessaging; } } diff --git a/packages/messaging/.eslintrc.js b/packages/messaging/.eslintrc.js index 16276950320..ca80aa0f69a 100644 --- a/packages/messaging/.eslintrc.js +++ b/packages/messaging/.eslintrc.js @@ -1,3 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + module.exports = { extends: '../../config/.eslintrc.js', parserOptions: { diff --git a/packages/messaging/.npmignore b/packages/messaging/.npmignore deleted file mode 100644 index 682c8f74a52..00000000000 --- a/packages/messaging/.npmignore +++ /dev/null @@ -1,9 +0,0 @@ -# Directories not needed by end users -/src -test - -# Files not needed by end users -gulpfile.js -index.ts -karma.conf.js -tsconfig.json \ No newline at end of file diff --git a/packages-exp/messaging-exp/api-extractor.json b/packages/messaging/api-extractor.json similarity index 100% rename from packages-exp/messaging-exp/api-extractor.json rename to packages/messaging/api-extractor.json diff --git a/packages/messaging/package.json b/packages/messaging/package.json index 5ac8f708c96..b79c527dc53 100644 --- a/packages/messaging/package.json +++ b/packages/messaging/package.json @@ -4,31 +4,40 @@ "description": "", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "typings": "dist/index.d.ts", + "sw": "dist/index.sw.esm2017.js", "files": [ - "dist" + "dist", + "sw/package.json" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c", - "build:deps": "lerna run --scope @firebase/messaging --include-dependencies build", + "build": "rollup -c && yarn api-report", + "build:deps": "lerna run --scope @firebase/'{app,messaging}' --include-dependencies build", + "build:release": "yarn build && yarn typings:public", "dev": "rollup -c -w", - "test": "run-p lint test:karma", - "test:integration": "test:karma && cd ../../integration/messaging && npm run-script test", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:karma", + "test": "run-p test:karma type-check lint ", + "test:integration": "run-p test:karma type-check lint && cd ../../integration/messaging && npm run-script test", + "test:ci": "node ../../scripts/run_tests_in_ci.js", "test:karma": "karma start --single-run", - "test:debug": "karma start --browsers=Chrome --auto-watch" + "test:debug": "karma start --browsers=Chrome --auto-watch", + "api-report": "yarn api-report:main && yarn api-report:sw && yarn api-report:api-json", + "api-report:main": "ts-node-script ../../repo-scripts/prune-dts/extract-public-api.ts --package messaging --packageRoot . --typescriptDts ./dist/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/index-public.d.ts", + "api-report:sw": "ts-node-script ../../repo-scripts/prune-dts/extract-public-api.ts --package messaging-sw --packageRoot . --typescriptDts ./dist/index.sw.d.ts --rollupDts ./dist/sw/private.d.ts --untrimmedRollupDts ./dist/sw/internal.d.ts --publicDts ./dist/sw/index-public.d.ts", + "api-report:api-json": "api-extractor run --local --verbose", + "type-check": "tsc --noEmit", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/index-public.d.ts" }, "license": "Apache-2.0", "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" }, "dependencies": { "@firebase/installations": "0.4.32", - "@firebase/messaging-types": "0.5.0", + "@firebase/messaging-interop-types": "0.0.1", "@firebase/util": "1.3.0", "@firebase/component": "0.5.6", "idb": "3.0.2", @@ -38,6 +47,7 @@ "@firebase/app": "0.6.30", "rollup": "2.52.2", "rollup-plugin-typescript2": "0.30.0", + "@rollup/plugin-json": "4.1.0", "ts-essentials": "7.0.1", "typescript": "4.2.2" }, @@ -49,5 +59,5 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/index.d.ts" + "esm5": "dist/index.esm.js" } diff --git a/packages/messaging/rollup.config.js b/packages/messaging/rollup.config.js index b1eaf62525a..5d7b3338874 100644 --- a/packages/messaging/rollup.config.js +++ b/packages/messaging/rollup.config.js @@ -16,9 +16,9 @@ */ import json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; import pkg from './package.json'; +import typescript from 'typescript'; +import typescriptPlugin from 'rollup-plugin-typescript2'; const deps = Object.keys( Object.assign({}, pkg.peerDependencies, pkg.dependencies) @@ -39,7 +39,7 @@ const es5Builds = [ input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } + { file: pkg.esm5, format: 'es', sourcemap: true } ], plugins: es5BuildPlugins, external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) @@ -65,12 +65,20 @@ const es2017Builds = [ { input: 'src/index.ts', output: { - file: pkg.esm2017, + file: pkg.browser, format: 'es', sourcemap: true }, plugins: es2017BuildPlugins, external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + // sw builds + { + input: 'src/index.sw.ts', + output: { file: pkg.sw, format: 'es', sourcemap: true }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } ]; + export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/messaging-exp/src/api.ts b/packages/messaging/src/api.ts similarity index 77% rename from packages-exp/messaging-exp/src/api.ts rename to packages/messaging/src/api.ts index 8b9a9d487bb..821c2fe759c 100644 --- a/packages-exp/messaging-exp/src/api.ts +++ b/packages/messaging/src/api.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp, _getProvider, getApp } from '@firebase/app-exp'; +import { FirebaseApp, _getProvider, getApp } from '@firebase/app'; import { GetTokenOptions, MessagePayload, @@ -31,9 +31,11 @@ import { import { MessagingService } from './messaging-service'; import { deleteToken as _deleteToken } from './api/deleteToken'; import { getToken as _getToken } from './api/getToken'; +import { isSwSupported, isWindowSupported } from './api/isSupported'; import { onBackgroundMessage as _onBackgroundMessage } from './api/onBackgroundMessage'; import { onMessage as _onMessage } from './api/onMessage'; import { _setDeliveryMetricsExportedToBigQueryEnabled } from './api/setDeliveryMetricsExportedToBigQueryEnabled'; +import { ERROR_FACTORY, ErrorCode } from './util/errors'; /** * Retrieves a Firebase Cloud Messaging instance. @@ -43,7 +45,23 @@ import { _setDeliveryMetricsExportedToBigQueryEnabled } from './api/setDeliveryM * @public */ export function getMessagingInWindow(app: FirebaseApp = getApp()): Messaging { - return _getProvider(getModularInstance(app), 'messaging-exp').getImmediate(); + // Conscious decision to make this async check non-blocking during the messaging instance + // initialization phase for performance consideration. An error would be thrown latter for + // developer's information. Developers can then choose to import and call `isSupported` for + // special handling. + isWindowSupported().then( + isSupported => { + // If `isWindowSupported()` resolved, but returned false. + if (!isSupported) { + throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); + } + }, + _ => { + // If `isWindowSupported()` rejected. + throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED); + } + ); + return _getProvider(getModularInstance(app), 'messaging').getImmediate(); } /** @@ -54,10 +72,23 @@ export function getMessagingInWindow(app: FirebaseApp = getApp()): Messaging { * @public */ export function getMessagingInSw(app: FirebaseApp = getApp()): Messaging { - return _getProvider( - getModularInstance(app), - 'messaging-sw-exp' - ).getImmediate(); + // Conscious decision to make this async check non-blocking during the messaging instance + // initialization phase for performance consideration. An error would be thrown latter for + // developer's information. Developers can then choose to import and call `isSupported` for + // special handling. + isSwSupported().then( + isSupported => { + // If `isSwSupported()` resolved, but returned false. + if (!isSupported) { + throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); + } + }, + _ => { + // If `isSwSupported()` rejected. + throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED); + } + ); + return _getProvider(getModularInstance(app), 'messaging-sw').getImmediate(); } /** diff --git a/packages-exp/messaging-exp/src/api/deleteToken.ts b/packages/messaging/src/api/deleteToken.ts similarity index 100% rename from packages-exp/messaging-exp/src/api/deleteToken.ts rename to packages/messaging/src/api/deleteToken.ts diff --git a/packages-exp/messaging-exp/src/api/getToken.ts b/packages/messaging/src/api/getToken.ts similarity index 100% rename from packages-exp/messaging-exp/src/api/getToken.ts rename to packages/messaging/src/api/getToken.ts diff --git a/packages-exp/messaging-exp/src/api/isSupported.ts b/packages/messaging/src/api/isSupported.ts similarity index 100% rename from packages-exp/messaging-exp/src/api/isSupported.ts rename to packages/messaging/src/api/isSupported.ts diff --git a/packages-exp/messaging-exp/src/api/onBackgroundMessage.ts b/packages/messaging/src/api/onBackgroundMessage.ts similarity index 100% rename from packages-exp/messaging-exp/src/api/onBackgroundMessage.ts rename to packages/messaging/src/api/onBackgroundMessage.ts diff --git a/packages-exp/messaging-exp/src/api/onMessage.ts b/packages/messaging/src/api/onMessage.ts similarity index 100% rename from packages-exp/messaging-exp/src/api/onMessage.ts rename to packages/messaging/src/api/onMessage.ts diff --git a/packages-exp/messaging-exp/src/api/setDeliveryMetricsExportedToBigQueryEnabled.ts b/packages/messaging/src/api/setDeliveryMetricsExportedToBigQueryEnabled.ts similarity index 100% rename from packages-exp/messaging-exp/src/api/setDeliveryMetricsExportedToBigQueryEnabled.ts rename to packages/messaging/src/api/setDeliveryMetricsExportedToBigQueryEnabled.ts diff --git a/packages/messaging/src/controllers/sw-controller.test.ts b/packages/messaging/src/controllers/sw-controller.test.ts deleted file mode 100644 index 91afaf1a329..00000000000 --- a/packages/messaging/src/controllers/sw-controller.test.ts +++ /dev/null @@ -1,608 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import * as tokenManagementModule from '../core/token-management'; - -import { BgMessageHandler, SwController } from './sw-controller'; -import { - CONSOLE_CAMPAIGN_ANALYTICS_ENABLED, - CONSOLE_CAMPAIGN_ID, - CONSOLE_CAMPAIGN_NAME, - CONSOLE_CAMPAIGN_TIME, - DEFAULT_VAPID_KEY, - FCM_MSG -} from '../util/constants'; -import { DeepPartial, ValueOf, Writable } from 'ts-essentials'; -import { - FakeEvent, - FakePushSubscription, - mockServiceWorker, - restoreServiceWorker -} from '../testing/fakes/service-worker'; -import { - MessagePayloadInternal, - MessageType -} from '../interfaces/internal-message-payload'; -import { spy, stub } from 'sinon'; - -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; -import { Stub } from '../testing/sinon-types'; -import { dbSet } from '../helpers/idb-manager'; -import { expect } from 'chai'; -import { getFakeFirebaseDependencies } from '../testing/fakes/firebase-dependencies'; -import { getFakeTokenDetails } from '../testing/fakes/token-details'; - -const LOCAL_HOST = self.location.host; -const TEST_LINK = 'https://' + LOCAL_HOST + '/test-link.org'; -const TEST_CLICK_ACTION = 'https://' + LOCAL_HOST + '/test-click-action.org'; - -// Add fake SW types. -declare const self: Window & Writable; - -// internal message payload (parsed directly from the push event) that contains and only contains -// notification payload. -const DISPLAY_MESSAGE: MessagePayloadInternal = { - notification: { - title: 'title', - body: 'body' - }, - fcmOptions: { - link: TEST_LINK - }, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' -}; - -// internal message payload (parsed directly from the push event) that contains and only contains -// data payload. -const DATA_MESSAGE: MessagePayloadInternal = { - data: { - key: 'value' - }, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' -}; - -describe('SwController', () => { - let addEventListenerStub: Stub; - // eslint-disable-next-line @typescript-eslint/ban-types - let eventListenerMap: Map; - let swController: SwController; - let firebaseDependencies: FirebaseInternalDependencies; - let getTokenStub: Stub; - let deleteTokenStub: Stub; - - beforeEach(() => { - mockServiceWorker(); - - stub(Notification, 'permission').value('granted'); - - // Instead of calling actual addEventListener, add the event to the eventListeners list. Actual - // event listeners can't be used as the tests are not running in a Service Worker, which means - // Push events do not exist. - addEventListenerStub = stub(self, 'addEventListener').callsFake( - (type, listener) => { - eventListenerMap.set(type, listener); - } - ); - eventListenerMap = new Map(); - - getTokenStub = stub(tokenManagementModule, 'getToken').resolves( - 'token-value' - ); - deleteTokenStub = stub(tokenManagementModule, 'deleteToken').resolves(true); - - firebaseDependencies = getFakeFirebaseDependencies(); - swController = new SwController(firebaseDependencies); - }); - - afterEach(() => { - restoreServiceWorker(); - }); - - it('has app', () => { - expect(swController.app).to.equal(firebaseDependencies.app); - }); - - it('sets event listeners on initialization', () => { - expect(addEventListenerStub).to.have.been.calledThrice; - expect(addEventListenerStub).to.have.been.calledWith('push'); - expect(addEventListenerStub).to.have.been.calledWith( - 'pushsubscriptionchange' - ); - expect(addEventListenerStub).to.have.been.calledWith('notificationclick'); - }); - - it('throws when window-only methods are called', () => { - expect(() => swController.requestPermission()).to.throw( - 'messaging/only-available-in-window' - ); - expect(() => swController.useServiceWorker()).to.throw( - 'messaging/only-available-in-window' - ); - expect(() => swController.onMessage()).to.throw( - 'messaging/only-available-in-window' - ); - expect(() => swController.onTokenRefresh()).to.throw( - 'messaging/only-available-in-window' - ); - }); - - describe('getToken', () => { - it('calls getToken with the set VAPID key', async () => { - swController.usePublicVapidKey('use-vapid-key'); - await swController.getToken(); - - expect(getTokenStub).to.have.been.calledWith( - firebaseDependencies, - self.registration, - 'use-vapid-key' - ); - }); - - it('calls getToken with the current VAPID key if it is not set', async () => { - const tokenDetails = getFakeTokenDetails(); - await dbSet(firebaseDependencies, tokenDetails); - - await swController.getToken(); - - expect(getTokenStub).to.have.been.calledWith( - firebaseDependencies, - self.registration, - 'dmFwaWQta2V5LXZhbHVl' - ); - }); - - it('calls getToken with the default VAPID key if there is no token in db', async () => { - await swController.getToken(); - - expect(getTokenStub).to.have.been.calledWith( - firebaseDependencies, - self.registration, - DEFAULT_VAPID_KEY - ); - }); - }); - - describe('deleteToken', () => { - it('calls deleteToken', async () => { - await swController.deleteToken(); - - expect(deleteTokenStub).to.have.been.calledWith( - firebaseDependencies, - self.registration - ); - }); - }); - - describe('onPush', () => { - it('does nothing if push is not from FCM', async () => { - const showNotificationSpy = spy(self.registration, 'showNotification'); - const matchAllSpy = spy(self.clients, 'matchAll'); - - await callEventListener(makeEvent('push', {})); - - await callEventListener( - makeEvent('push', { - data: {} - }) - ); - - expect(showNotificationSpy).not.to.have.been.called; - expect(matchAllSpy).not.to.have.been.called; - }); - - it('sends a message to window clients if a window client is visible', async () => { - const client: Writable = (await self.clients.openWindow( - 'https://example.org' - ))!; - client.visibilityState = 'visible'; - const postMessageSpy = spy(client, 'postMessage'); - - await callEventListener( - makeEvent('push', { - data: { - json: () => DISPLAY_MESSAGE - } - }) - ); - - const expectedMessage: MessagePayloadInternal = { - ...DISPLAY_MESSAGE, - messageType: MessageType.PUSH_RECEIVED - }; - expect(postMessageSpy).to.have.been.calledOnceWith(expectedMessage); - }); - - it('does not send a message to window clients if window clients are hidden', async () => { - const client = (await self.clients.openWindow('https://example.org'))!; - const postMessageSpy = spy(client, 'postMessage'); - const showNotificationSpy = spy(self.registration, 'showNotification'); - - await callEventListener( - makeEvent('push', { - data: { - json: () => DISPLAY_MESSAGE - } - }) - ); - - expect(postMessageSpy).not.to.have.been.called; - expect(showNotificationSpy).to.have.been.calledWith('title', { - ...DISPLAY_MESSAGE.notification, - data: { - [FCM_MSG]: DISPLAY_MESSAGE - } - }); - }); - - it('displays a notification if a window client does not exist', async () => { - const showNotificationSpy = spy(self.registration, 'showNotification'); - - await callEventListener( - makeEvent('push', { - data: { - json: () => DISPLAY_MESSAGE - } - }) - ); - - expect(showNotificationSpy).to.have.been.calledWith('title', { - ...DISPLAY_MESSAGE.notification, - data: { - ...DISPLAY_MESSAGE.notification!.data, - [FCM_MSG]: DISPLAY_MESSAGE - } - }); - }); - - it('calls bgMessageHandler if message is not a notification', async () => { - const bgMessageHandlerSpy = spy(); - swController.setBackgroundMessageHandler(bgMessageHandlerSpy); - - await callEventListener( - makeEvent('push', { - data: { - json: () => DATA_MESSAGE - } - }) - ); - - expect(bgMessageHandlerSpy).to.have.been.calledWith(); - }); - - it('forwards MessagePayload with a notification payload to onBackgroundMessage', async () => { - const bgMessageHandlerSpy = spy(); - const showNotificationSpy = spy(self.registration, 'showNotification'); - - swController.onBackgroundMessage(bgMessageHandlerSpy); - - await callEventListener( - makeEvent('push', { - data: { - json: () => ({ - notification: { - ...DISPLAY_MESSAGE - }, - data: { - ...DATA_MESSAGE - } - }) - } - }) - ); - - expect(bgMessageHandlerSpy).to.have.been.called; - expect(showNotificationSpy).to.have.been.called; - }); - - it('warns if there are more action buttons than the browser limit', async () => { - // This doesn't exist on Firefox: - // https://developer.mozilla.org/en-US/docs/Web/API/notification/maxActions - if (!Notification.maxActions) { - return; - } - stub(Notification, 'maxActions').value(1); - - const warnStub = stub(console, 'warn'); - - await callEventListener( - makeEvent('push', { - data: { - json: () => ({ - notification: { - ...DISPLAY_MESSAGE, - actions: [ - { action: 'like', title: 'Like' }, - { action: 'favorite', title: 'Favorite' } - ] - } - }) - } - }) - ); - - expect(warnStub).to.have.been.calledOnceWith( - 'This browser only supports 1 actions. The remaining actions will not be displayed.' - ); - }); - }); - - describe('setBackgroundMessageHandler', () => { - it('throws on invalid input', () => { - expect(() => - swController.setBackgroundMessageHandler( - null as unknown as BgMessageHandler - ) - ).to.throw('messaging/invalid-bg-handler'); - }); - }); - - describe('usePublicVapidKey', () => { - it('throws on invalid input', () => { - expect(() => - swController.usePublicVapidKey(null as unknown as string) - ).to.throw('messaging/invalid-vapid-key'); - - expect(() => swController.usePublicVapidKey('')).to.throw( - 'messaging/invalid-vapid-key' - ); - }); - - it('throws if called twice', () => { - swController.usePublicVapidKey('dmFwaWQta2V5LXZhbHVl'); - expect(() => - swController.usePublicVapidKey('dmFwaWQta2V5LXZhbHVl') - ).to.throw('messaging/use-vapid-key-after-get-token'); - }); - - it('throws if called after getToken', async () => { - await swController.getToken(); - - expect(() => - swController.usePublicVapidKey('dmFwaWQta2V5LXZhbHVl') - ).to.throw('messaging/use-vapid-key-after-get-token'); - }); - }); - - describe('onNotificationClick', () => { - let NOTIFICATION_CLICK_PAYLOAD: DeepPartial; - - beforeEach(() => { - NOTIFICATION_CLICK_PAYLOAD = { - notification: new Notification('title', { - ...DISPLAY_MESSAGE.notification, - data: { - ...DISPLAY_MESSAGE.notification!.data, - [FCM_MSG]: DISPLAY_MESSAGE - } - }) - }; - }); - - it('does nothing if notification is not from FCM', async () => { - delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG]; - - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - const stopImmediatePropagationSpy = spy( - event, - 'stopImmediatePropagation' - ); - - await callEventListener(event); - - expect(stopImmediatePropagationSpy).not.to.have.been.called; - }); - - it('does nothing if an action button was clicked', async () => { - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - event.action = 'actionName'; - const stopImmediatePropagationSpy = spy( - event, - 'stopImmediatePropagation' - ); - - await callEventListener(event); - - expect(stopImmediatePropagationSpy).not.to.have.been.called; - }); - - it('calls stopImmediatePropagation and notification.close', async () => { - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - const stopImmediatePropagationSpy = spy( - event, - 'stopImmediatePropagation' - ); - const notificationCloseSpy = spy(event.notification, 'close'); - - await callEventListener(event); - - expect(stopImmediatePropagationSpy).to.have.been.called; - expect(notificationCloseSpy).to.have.been.called; - }); - - it('does not redirect if there is no link', async () => { - // Remove link. - delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions; - - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - const stopImmediatePropagationSpy = spy( - event, - 'stopImmediatePropagation' - ); - const notificationCloseSpy = spy(event.notification, 'close'); - const matchAllSpy = spy(self.clients, 'matchAll'); - - await callEventListener(event); - - expect(stopImmediatePropagationSpy).to.have.been.called; - expect(notificationCloseSpy).to.have.been.called; - expect(matchAllSpy).not.to.have.been.called; - }); - - it('does not redirect if link is not from origin', async () => { - // Remove link. - NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions.link = - 'https://www.youtube.com'; - - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - const stopImmediatePropagationSpy = spy( - event, - 'stopImmediatePropagation' - ); - const notificationCloseSpy = spy(event.notification, 'close'); - const matchAllSpy = spy(self.clients, 'matchAll'); - - await callEventListener(event); - - expect(stopImmediatePropagationSpy).to.have.been.called; - expect(notificationCloseSpy).to.have.been.called; - expect(matchAllSpy).not.to.have.been.called; - }); - - it('focuses on and sends the message to an open WindowClient', async () => { - const client: Writable = (await self.clients.openWindow( - TEST_LINK - ))!; - const focusSpy = spy(client, 'focus'); - const matchAllSpy = spy(self.clients, 'matchAll'); - const openWindowSpy = spy(self.clients, 'openWindow'); - const postMessageSpy = spy(client, 'postMessage'); - - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - - await callEventListener(event); - - expect(matchAllSpy).to.have.been.called; - expect(openWindowSpy).not.to.have.been.called; - expect(focusSpy).to.have.been.called; - expect(postMessageSpy).to.have.been.calledWith({ - ...DISPLAY_MESSAGE, - messageType: MessageType.NOTIFICATION_CLICKED - }); - }); - - it("opens a new client if there isn't one already open", async () => { - const matchAllSpy = spy(self.clients, 'matchAll'); - const openWindowSpy = spy(self.clients, 'openWindow'); - - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - - await callEventListener(event); - - expect(matchAllSpy).to.have.been.called; - expect(openWindowSpy).to.have.been.calledWith(TEST_LINK); - }); - - it('works with click_action', async () => { - // Replace link with the deprecated click_action. - delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions; - NOTIFICATION_CLICK_PAYLOAD.notification!.data![ - FCM_MSG - ].notification.click_action = TEST_CLICK_ACTION; // eslint-disable-line camelcase - - const matchAllSpy = spy(self.clients, 'matchAll'); - const openWindowSpy = spy(self.clients, 'openWindow'); - - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - - await callEventListener(event); - - expect(matchAllSpy).to.have.been.called; - expect(openWindowSpy).to.have.been.calledWith(TEST_CLICK_ACTION); - }); - - it('redirects to origin if message was sent from the FN Console', async () => { - // Remove link. - delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions; - // Add FN data. - NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].data = { - [CONSOLE_CAMPAIGN_ID]: '123456', - [CONSOLE_CAMPAIGN_NAME]: 'Campaign Name', - [CONSOLE_CAMPAIGN_TIME]: '1234567890', - [CONSOLE_CAMPAIGN_ANALYTICS_ENABLED]: '1' - }; - - const matchAllSpy = spy(self.clients, 'matchAll'); - const openWindowSpy = spy(self.clients, 'openWindow'); - - const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); - - await callEventListener(event); - - expect(matchAllSpy).to.have.been.called; - expect(openWindowSpy).to.have.been.calledWith(self.location.origin); - }); - }); - - describe('onSubChange', () => { - it('calls deleteToken if there is no new subscription', async () => { - const event = makeEvent('pushsubscriptionchange', { - oldSubscription: new FakePushSubscription(), - newSubscription: undefined - }); - - await callEventListener(event); - - expect(deleteTokenStub).to.have.been.called; - expect(getTokenStub).not.to.have.been.called; - }); - - it('calls deleteToken and getToken if subscription changed', async () => { - const event = makeEvent('pushsubscriptionchange', { - oldSubscription: new FakePushSubscription(), - newSubscription: new FakePushSubscription() - }); - - await callEventListener(event); - - expect(deleteTokenStub).to.have.been.called; - expect(getTokenStub).to.have.been.called; - }); - }); - - async function callEventListener( - event: ValueOf - ): Promise { - const listener = eventListenerMap.get(event.type); - if (!listener) { - throw new Error(`Event listener for ${event.type} was not defined.`); - } - - const waitUntil = spy(event, 'waitUntil'); - listener(event); - await waitUntil.getCall(0).args[0]; - } -}); - -/** Makes fake push events. */ -function makeEvent( - type: K, - data: DeepPartial -): Writable { - const event = new FakeEvent(type); - Object.assign(event, data); - return event as unknown as ServiceWorkerGlobalScopeEventMap[K]; -} diff --git a/packages/messaging/src/controllers/sw-controller.ts b/packages/messaging/src/controllers/sw-controller.ts deleted file mode 100644 index c4b9b7a024e..00000000000 --- a/packages/messaging/src/controllers/sw-controller.ts +++ /dev/null @@ -1,411 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - BACKGROUND_HANDLE_EXECUTION_TIME_LIMIT_MS, - DEFAULT_VAPID_KEY, - FCM_MSG, - FOREGROUND_HANDLE_PREPARATION_TIME_MS, - TAG -} from '../util/constants'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { FirebaseMessaging, MessagePayload } from '@firebase/messaging-types'; -import { - MessagePayloadInternal, - MessageType, - NotificationPayloadInternal -} from '../interfaces/internal-message-payload'; -import { NextFn, Observer, Unsubscribe } from '@firebase/util'; -import { deleteToken, getToken } from '../core/token-management'; - -import { FirebaseApp } from '@firebase/app-types'; -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; -import { FirebaseService } from '@firebase/app-types/private'; -import { dbGet } from '../helpers/idb-manager'; -import { externalizePayload } from '../helpers/externalizePayload'; -import { isConsoleMessage } from '../helpers/is-console-message'; -import { sleep } from '../helpers/sleep'; - -// Let TS know that this is a service worker -declare const self: ServiceWorkerGlobalScope; - -export type BgMessageHandler = (payload: MessagePayload) => unknown; - -export class SwController implements FirebaseMessaging, FirebaseService { - // A boolean flag to determine wether an app is using onBackgroundMessage or - // setBackgroundMessageHandler. onBackgroundMessage will receive a MessagePayload regardless of if - // a notification is displayed. Whereas, setBackgroundMessageHandler will swallow the - // MessagePayload if a NotificationPayload is included. - private isOnBackgroundMessageUsed: boolean | null = null; - private vapidKey: string | null = null; - private bgMessageHandler: - | null - | BgMessageHandler - | NextFn - | Observer = null; - - constructor( - private readonly firebaseDependencies: FirebaseInternalDependencies - ) { - self.addEventListener('push', e => { - e.waitUntil(this.onPush(e)); - }); - self.addEventListener('pushsubscriptionchange', e => { - e.waitUntil(this.onSubChange(e)); - }); - self.addEventListener('notificationclick', e => { - e.waitUntil(this.onNotificationClick(e)); - }); - } - - get app(): FirebaseApp { - return this.firebaseDependencies.app; - } - - /** - * @deprecated. Use onBackgroundMessage(nextOrObserver: NextFn | Observer): - * Unsubscribe instead. - * - * Calling setBackgroundMessageHandler will opt in to some specific behaviors. - * - * 1.) If a notification doesn't need to be shown due to a window already being visible, then push - * messages will be sent to the page. 2.) If a notification needs to be shown, and the message - * contains no notification data this method will be called and the promise it returns will be - * passed to event.waitUntil. If you do not set this callback then all push messages will let and - * the developer can handle them in a their own 'push' event callback - * - * @param callback The callback to be called when a push message is received and a notification - * must be shown. The callback will be given the data from the push message. - */ - setBackgroundMessageHandler(callback: BgMessageHandler): void { - this.isOnBackgroundMessageUsed = false; - - if (!callback || typeof callback !== 'function') { - throw ERROR_FACTORY.create(ErrorCode.INVALID_BG_HANDLER); - } - - this.bgMessageHandler = callback; - } - - onBackgroundMessage( - nextOrObserver: NextFn | Observer - ): Unsubscribe { - this.isOnBackgroundMessageUsed = true; - this.bgMessageHandler = nextOrObserver; - - return () => { - this.bgMessageHandler = null; - }; - } - - // TODO: Remove getToken from SW Controller. Calling this from an old SW can cause all kinds of - // trouble. - async getToken(): Promise { - if (!this.vapidKey) { - // Call getToken using the current VAPID key if there already is a token. This is needed - // because usePublicVapidKey was not available in SW. It will be removed when vapidKey becomes - // a parameter of getToken, or when getToken is removed from SW. - const tokenDetails = await dbGet(this.firebaseDependencies); - this.vapidKey = - tokenDetails?.subscriptionOptions?.vapidKey ?? DEFAULT_VAPID_KEY; - } - - return getToken( - this.firebaseDependencies, - self.registration, - this.vapidKey - ); - } - - // TODO: Remove deleteToken from SW Controller. Calling this from an old SW can cause all kinds of - // trouble. - deleteToken(): Promise { - return deleteToken(this.firebaseDependencies, self.registration); - } - - requestPermission(): Promise { - throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_WINDOW); - } - - // TODO: Remove this together with getToken from SW Controller. - usePublicVapidKey(vapidKey: string): void { - if (this.vapidKey !== null) { - throw ERROR_FACTORY.create(ErrorCode.USE_VAPID_KEY_AFTER_GET_TOKEN); - } - - if (typeof vapidKey !== 'string' || vapidKey.length === 0) { - throw ERROR_FACTORY.create(ErrorCode.INVALID_VAPID_KEY); - } - - this.vapidKey = vapidKey; - } - - useServiceWorker(): void { - throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_WINDOW); - } - - onMessage(): Unsubscribe { - throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_WINDOW); - } - - onTokenRefresh(): Unsubscribe { - throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_WINDOW); - } - - /** - * A handler for push events that shows notifications based on the content of the payload. - * - * The payload must be a JSON-encoded Object with a `notification` key. The value of the - * `notification` property will be used as the NotificationOptions object passed to - * showNotification. Additionally, the `title` property of the notification object will be used as - * the title. - * - * If there is no notification data in the payload then no notification will be shown. - */ - async onPush(event: PushEvent): Promise { - const internalPayload = getMessagePayloadInternal(event); - if (!internalPayload) { - console.debug( - TAG + - 'failed to get parsed MessagePayload from the PushEvent. Skip handling the push.' - ); - return; - } - - // foreground handling: eventually passed to onMessage hook - const clientList = await getClientList(); - if (hasVisibleClients(clientList)) { - return sendMessagePayloadInternalToWindows(clientList, internalPayload); - } - - // background handling: display and pass to onBackgroundMessage hook - let isNotificationShown = false; - if (!!internalPayload.notification) { - await showNotification(wrapInternalPayload(internalPayload)); - isNotificationShown = true; - } - - // MessagePayload is only passed to `onBackgroundMessage`. Skip passing MessagePayload for - // the legacy `setBackgroundMessageHandler` to preserve the SDK behaviors. - if ( - isNotificationShown === true && - this.isOnBackgroundMessageUsed === false - ) { - return; - } - - if (!!this.bgMessageHandler) { - const payload = externalizePayload(internalPayload); - - if (typeof this.bgMessageHandler === 'function') { - this.bgMessageHandler(payload); - } else { - this.bgMessageHandler.next(payload); - } - } - - // wait briefly to allow onBackgroundMessage to complete - await sleep(BACKGROUND_HANDLE_EXECUTION_TIME_LIMIT_MS); - } - - async onSubChange(event: PushSubscriptionChangeEvent): Promise { - const { newSubscription } = event; - if (!newSubscription) { - // Subscription revoked, delete token - await deleteToken(this.firebaseDependencies, self.registration); - return; - } - - const tokenDetails = await dbGet(this.firebaseDependencies); - await deleteToken(this.firebaseDependencies, self.registration); - await getToken( - this.firebaseDependencies, - self.registration, - tokenDetails?.subscriptionOptions?.vapidKey ?? DEFAULT_VAPID_KEY - ); - } - - async onNotificationClick(event: NotificationEvent): Promise { - const internalPayload: MessagePayloadInternal = - event.notification?.data?.[FCM_MSG]; - - if (!internalPayload) { - return; - } else if (event.action) { - // User clicked on an action button. This will allow developers to act on action button clicks - // by using a custom onNotificationClick listener that they define. - return; - } - - // Prevent other listeners from receiving the event - event.stopImmediatePropagation(); - event.notification.close(); - - // Note clicking on a notification with no link set will focus the Chrome's current tab. - const link = getLink(internalPayload); - if (!link) { - return; - } - - // FM should only open/focus links from app's origin. - const url = new URL(link, self.location.href); - const originUrl = new URL(self.location.origin); - - if (url.host !== originUrl.host) { - return; - } - - let client = await getWindowClient(url); - - if (!client) { - client = await self.clients.openWindow(link); - - // Wait three seconds for the client to initialize and set up the message handler so that it - // can receive the message. - await sleep(FOREGROUND_HANDLE_PREPARATION_TIME_MS); - } else { - client = await client.focus(); - } - - if (!client) { - // Window Client will not be returned if it's for a third party origin. - return; - } - - internalPayload.messageType = MessageType.NOTIFICATION_CLICKED; - internalPayload.isFirebaseMessaging = true; - return client.postMessage(internalPayload); - } -} - -function wrapInternalPayload( - internalPayload: MessagePayloadInternal -): NotificationPayloadInternal { - const wrappedInternalPayload: NotificationPayloadInternal = { - ...((internalPayload.notification as unknown) as NotificationPayloadInternal) - }; - - // Put the message payload under FCM_MSG name so we can identify the notification as being an FCM - // notification vs a notification from somewhere else (i.e. normal web push or developer generated - // notification). - wrappedInternalPayload.data = { - [FCM_MSG]: internalPayload - }; - - return wrappedInternalPayload; -} - -function getMessagePayloadInternal({ - data -}: PushEvent): MessagePayloadInternal | null { - if (!data) { - return null; - } - - try { - return data.json(); - } catch (err) { - // Not JSON so not an FCM message. - return null; - } -} - -/** - * @param url The URL to look for when focusing a client. - * @return Returns an existing window client or a newly opened WindowClient. - */ -async function getWindowClient(url: URL): Promise { - const clientList = await getClientList(); - - for (const client of clientList) { - const clientUrl = new URL(client.url, self.location.href); - - if (url.host === clientUrl.host) { - return client; - } - } - - return null; -} - -/** - * @returns If there is currently a visible WindowClient, this method will resolve to true, - * otherwise false. - */ -function hasVisibleClients(clientList: WindowClient[]): boolean { - return clientList.some( - client => - client.visibilityState === 'visible' && - // Ignore chrome-extension clients as that matches the background pages of extensions, which - // are always considered visible for some reason. - !client.url.startsWith('chrome-extension://') - ); -} - -function sendMessagePayloadInternalToWindows( - clientList: WindowClient[], - internalPayload: MessagePayloadInternal -): void { - internalPayload.isFirebaseMessaging = true; - internalPayload.messageType = MessageType.PUSH_RECEIVED; - - for (const client of clientList) { - client.postMessage(internalPayload); - } -} - -function getClientList(): Promise { - return self.clients.matchAll({ - type: 'window', - includeUncontrolled: true - // TS doesn't know that "type: 'window'" means it'll return WindowClient[] - }) as Promise; -} - -function showNotification( - notificationPayloadInternal: NotificationPayloadInternal -): Promise { - // Note: Firefox does not support the maxActions property. - // https://developer.mozilla.org/en-US/docs/Web/API/notification/maxActions - const { actions } = notificationPayloadInternal; - const { maxActions } = Notification; - if (actions && maxActions && actions.length > maxActions) { - console.warn( - `This browser only supports ${maxActions} actions. The remaining actions will not be displayed.` - ); - } - - return self.registration.showNotification( - /* title= */ notificationPayloadInternal.title ?? '', - notificationPayloadInternal - ); -} - -function getLink(payload: MessagePayloadInternal): string | null { - // eslint-disable-next-line camelcase - const link = payload.fcmOptions?.link ?? payload.notification?.click_action; - if (link) { - return link; - } - - if (isConsoleMessage(payload.data)) { - // Notification created in the Firebase Console. Redirect to origin. - return self.location.origin; - } else { - return null; - } -} diff --git a/packages/messaging/src/controllers/window-controller.test.ts b/packages/messaging/src/controllers/window-controller.test.ts deleted file mode 100644 index 423f2a15888..00000000000 --- a/packages/messaging/src/controllers/window-controller.test.ts +++ /dev/null @@ -1,659 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import * as tokenManagementModule from '../core/token-management'; - -import { - CONSOLE_CAMPAIGN_ANALYTICS_ENABLED, - CONSOLE_CAMPAIGN_ID, - CONSOLE_CAMPAIGN_NAME, - CONSOLE_CAMPAIGN_TIME, - DEFAULT_SW_PATH, - DEFAULT_SW_SCOPE, - DEFAULT_VAPID_KEY -} from '../util/constants'; -import { - MessagePayloadInternal, - MessageType -} from '../interfaces/internal-message-payload'; -import { SinonFakeTimers, SinonSpy, spy, stub, useFakeTimers } from 'sinon'; -import { Spy, Stub } from '../testing/sinon-types'; - -import { ErrorCode } from '../util/errors'; -import { FakeServiceWorkerRegistration } from '../testing/fakes/service-worker'; -import { FirebaseAnalyticsInternal } from '@firebase/analytics-interop-types'; -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; -import { WindowController } from './window-controller'; -import { expect } from 'chai'; -import { getFakeFirebaseDependencies } from '../testing/fakes/firebase-dependencies'; - -type MessageEventListener = (event: Event) => Promise; - -const ORIGINAL_SW_REGISTRATION = FakeServiceWorkerRegistration; - -describe('WindowController', () => { - let firebaseDependencies: FirebaseInternalDependencies; - let swRegistration: ServiceWorkerRegistration; - let windowController: WindowController; - - let getTokenStub: Stub; - let deleteTokenStub: Stub; - let registerStub: Stub; - let addEventListenerStub: Stub< - typeof navigator.serviceWorker.addEventListener - >; - - /** The event listener that WindowController adds to the message event. */ - let messageEventListener: MessageEventListener; - - beforeEach(() => { - // To trick the instanceof check in useServiceWorker. - self.ServiceWorkerRegistration = FakeServiceWorkerRegistration; - - firebaseDependencies = getFakeFirebaseDependencies(); - swRegistration = new FakeServiceWorkerRegistration(); - - stub(Notification, 'permission').value('granted'); - registerStub = stub(navigator.serviceWorker, 'register').resolves( - swRegistration - ); - addEventListenerStub = stub( - navigator.serviceWorker, - 'addEventListener' - ).callsFake((type, listener) => { - expect(type).to.equal('message'); - - if ('handleEvent' in listener) { - messageEventListener = listener.handleEvent as MessageEventListener; - } else { - messageEventListener = listener as MessageEventListener; - } - }); - getTokenStub = stub(tokenManagementModule, 'getToken').resolves('fcmToken'); - deleteTokenStub = stub(tokenManagementModule, 'deleteToken').resolves(true); - - windowController = new WindowController(firebaseDependencies); - }); - - afterEach(() => { - self.ServiceWorkerRegistration = ORIGINAL_SW_REGISTRATION; - }); - - it('has app', () => { - expect(windowController.app).to.equal(firebaseDependencies.app); - }); - - it('adds the message event listener on creation', () => { - expect(addEventListenerStub).to.have.been.called; - }); - - it('throws when service-worker-only methods are called', () => { - expect(() => windowController.setBackgroundMessageHandler()).to.throw( - 'messaging/only-available-in-sw' - ); - }); - - describe('getToken', () => { - it('uses default sw if none was registered nor provided', async () => { - expect(windowController.getSwReg()).to.be.undefined; - - await windowController.getToken({}); - - expect(registerStub).to.have.been.calledOnceWith(DEFAULT_SW_PATH, { - scope: DEFAULT_SW_SCOPE - }); - }); - - it('uses option-provided swReg if non was registered', async () => { - expect(windowController.getSwReg()).to.be.undefined; - - await windowController.getToken({ - serviceWorkerRegistration: swRegistration - }); - - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration, - DEFAULT_VAPID_KEY - ); - }); - - it('uses previously stored sw if non is provided in the option parameter', async () => { - windowController.useServiceWorker(swRegistration); - expect(windowController.getSwReg()).to.be.deep.equal(swRegistration); - - await windowController.getToken({}); - - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration, - DEFAULT_VAPID_KEY - ); - }); - - it('new swReg overrides existing swReg ', async () => { - windowController.useServiceWorker(swRegistration); - expect(windowController.getSwReg()).to.be.deep.equal(swRegistration); - - const otherSwReg = new FakeServiceWorkerRegistration(); - - await windowController.getToken({ - serviceWorkerRegistration: otherSwReg - }); - - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - otherSwReg, - DEFAULT_VAPID_KEY - ); - }); - - it('uses default VAPID if: a) no VAPID was stored and b) none is provided in option', async () => { - expect(windowController.getVapidKey()).is.null; - - await windowController.getToken({}); - - expect(windowController.getVapidKey()).to.equal(DEFAULT_VAPID_KEY); - - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration, - DEFAULT_VAPID_KEY - ); - }); - - it('uses option-provided VAPID if no VAPID has been registered', async () => { - expect(windowController.getVapidKey()).is.null; - - await windowController.getToken({ vapidKey: 'test_vapid_key' }); - - expect(windowController.getVapidKey()).to.equal('test_vapid_key'); - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration, - 'test_vapid_key' - ); - }); - - it('uses option-provided VAPID if it is different from currently registered VAPID', async () => { - windowController.usePublicVapidKey('old_key'); - expect(windowController.getVapidKey()).to.equal('old_key'); - - await windowController.getToken({ vapidKey: 'new_key' }); - - expect(windowController.getVapidKey()).to.equal('new_key'); - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration, - 'new_key' - ); - }); - - it('uses existing VAPID if newly provided has the same value', async () => { - windowController.usePublicVapidKey('key'); - expect(windowController.getVapidKey()).to.equal('key'); - - await windowController.getToken({ vapidKey: 'key' }); - - expect(windowController.getVapidKey()).to.equal('key'); - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration, - 'key' - ); - }); - - it('uses existing VAPID if non is provided in the option parameter', async () => { - windowController.usePublicVapidKey('key'); - expect(windowController.getVapidKey()).to.equal('key'); - - await windowController.getToken({}); - - expect(windowController.getVapidKey()).to.equal('key'); - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration, - 'key' - ); - }); - - it('throws if permission is denied', async () => { - stub(Notification, 'permission').value('denied'); - - try { - await windowController.getToken(); - throw new Error('should have thrown'); - } catch (err) { - expect(err.code).to.equal(`messaging/${ErrorCode.PERMISSION_BLOCKED}`); - } - }); - - it('asks for permission if permission is default', async () => { - stub(Notification, 'permission').value('default'); - const requestPermissionStub = stub( - Notification, - 'requestPermission' - ).resolves('denied'); - - try { - await windowController.getToken(); - throw new Error('should have thrown'); - } catch (err) { - expect(err.code).to.equal(`messaging/${ErrorCode.PERMISSION_BLOCKED}`); - } - - expect(requestPermissionStub).to.have.been.calledOnce; - }); - - it('registers the default SW', async () => { - await windowController.getToken(); - - expect(registerStub).to.have.been.calledOnceWith(DEFAULT_SW_PATH, { - scope: DEFAULT_SW_SCOPE - }); - }); - - it('throws if there is a failure to get SW registration', async () => { - registerStub.rejects(); - - try { - await windowController.getToken(); - throw new Error('should have thrown'); - } catch (err) { - expect(err.code).to.equal( - `messaging/${ErrorCode.FAILED_DEFAULT_REGISTRATION}` - ); - } - }); - - it('calls tokenManagement.getToken with the default SW and VAPID key', async () => { - await windowController.getToken(); - - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration, - DEFAULT_VAPID_KEY - ); - }); - - it('calls tokenManagement.getToken with the specified SW and VAPID key', async () => { - const differentSwRegistration = new FakeServiceWorkerRegistration(); - differentSwRegistration.scope = '/different-scope'; - - windowController.usePublicVapidKey('newVapidKey'); - windowController.useServiceWorker(differentSwRegistration); - await windowController.getToken(); - - expect(getTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - differentSwRegistration, - 'newVapidKey' - ); - }); - }); - - describe('deleteToken', () => { - it('calls tokenManagement.deleteToken with the default SW', async () => { - await windowController.deleteToken(); - - expect(deleteTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - swRegistration - ); - }); - - it('calls tokenManagement.deleteToken with the specified SW', async () => { - const differentSwRegistration = new FakeServiceWorkerRegistration(); - differentSwRegistration.scope = '/different-scope'; - - windowController.useServiceWorker(differentSwRegistration); - await windowController.deleteToken(); - - expect(deleteTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - differentSwRegistration - ); - }); - }); - - describe('requestPermission', () => { - it('should resolve if the permission is already granted', async () => { - stub(Notification, 'permission').value('granted'); - - return windowController.requestPermission(); - }); - - it('should reject if requestPermission resolves with "denied"', async () => { - stub(Notification, 'permission').value('default'); - stub(Notification, 'requestPermission').resolves('denied'); - - try { - await windowController.requestPermission(); - throw new Error('Expected an error.'); - } catch (err) { - expect(err.code).to.equal('messaging/permission-blocked'); - } - }); - - it('should reject if requestPermission resolves with "default"', async () => { - stub(Notification, 'permission').value('default'); - stub(Notification, 'requestPermission').resolves('default'); - - try { - await windowController.requestPermission(); - throw new Error('Expected an error.'); - } catch (err) { - expect(err.code).to.equal('messaging/permission-default'); - } - }); - - it('should resolve if requestPermission resolves with "granted"', async () => { - stub(Notification, 'permission').value('default'); - stub(Notification, 'requestPermission').resolves('granted'); - - return windowController.requestPermission(); - }); - }); - - describe('onMessage', () => { - it('sets the onMessage callback', async () => { - const onMessageCallback = spy(); - windowController.onMessage(onMessageCallback); - - const internalPayload: MessagePayloadInternal = { - notification: { title: 'hello', body: 'world' }, - messageType: MessageType.PUSH_RECEIVED, - isFirebaseMessaging: true, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - await messageEventListener( - new MessageEvent('message', { data: internalPayload }) - ); - - expect(onMessageCallback).to.have.been.called; - }); - - it('works with an observer', async () => { - const onMessageCallback = spy(); - windowController.onMessage({ - next: onMessageCallback, - error: () => {}, - complete: () => {} - }); - - const internalPayload: MessagePayloadInternal = { - notification: { title: 'hello', body: 'world' }, - messageType: MessageType.PUSH_RECEIVED, - isFirebaseMessaging: true, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - await messageEventListener( - new MessageEvent('message', { data: internalPayload }) - ); - - expect(onMessageCallback).to.have.been.called; - }); - - it('returns a function that clears the onMessage callback', async () => { - const onMessageCallback = spy(); - const unsubscribe = windowController.onMessage(onMessageCallback); - unsubscribe(); - - const internalPayload: MessagePayloadInternal = { - notification: { title: 'hello', body: 'world' }, - messageType: MessageType.PUSH_RECEIVED, - isFirebaseMessaging: true, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - await messageEventListener( - new MessageEvent('message', { data: internalPayload }) - ); - - expect(onMessageCallback).not.to.have.been.called; - }); - }); - - describe('usePublicVapidKey', () => { - it('throws on invalid input', () => { - expect(() => - windowController.usePublicVapidKey(null as unknown as string) - ).to.throw('messaging/invalid-vapid-key'); - - expect(() => windowController.usePublicVapidKey('')).to.throw( - 'messaging/invalid-vapid-key' - ); - }); - - it('throws if called twice', () => { - windowController.usePublicVapidKey('dmFwaWQta2V5LXZhbHVl'); - expect(() => - windowController.usePublicVapidKey('dmFwaWQta2V5LXZhbHVl') - ).to.throw('messaging/use-vapid-key-after-get-token'); - }); - - it('throws if called after getToken', async () => { - await windowController.getToken(); - - expect(() => - windowController.usePublicVapidKey('dmFwaWQta2V5LXZhbHVl') - ).to.throw('messaging/use-vapid-key-after-get-token'); - }); - }); - - describe('useServiceWorker', () => { - it('throws on invalid input', () => { - expect(() => - windowController.useServiceWorker( - {} as unknown as ServiceWorkerRegistration - ) - ).to.throw('messaging/invalid-sw-registration'); - }); - - it('throws if called twice', () => { - windowController.useServiceWorker(swRegistration); - expect(() => windowController.useServiceWorker(swRegistration)).to.throw( - 'messaging/use-sw-after-get-token' - ); - }); - - it('throws if called after getToken', async () => { - await windowController.getToken(); - - expect(() => windowController.useServiceWorker(swRegistration)).to.throw( - 'messaging/use-sw-after-get-token' - ); - }); - }); - - describe('SW message event handler', () => { - let clock: SinonFakeTimers; - let onMessageSpy: SinonSpy; - let logEventSpy: Spy; - - beforeEach(() => { - clock = useFakeTimers(); - - const analytics = firebaseDependencies.analyticsProvider.getImmediate(); - logEventSpy = spy(analytics, 'logEvent'); - - onMessageSpy = spy(); - windowController.onMessage(onMessageSpy); - }); - - it('does nothing when non-fcm message is passed in', async () => { - await messageEventListener( - new MessageEvent('message', { data: 'non-fcm-message' }) - ); - - expect(onMessageSpy).not.to.have.been.called; - expect(logEventSpy).not.to.have.been.called; - }); - - it('calls onMessage callback when it receives a PUSH_RECEIVED message', async () => { - const internalPayload: MessagePayloadInternal = { - notification: { title: 'hello', body: 'world' }, - messageType: MessageType.PUSH_RECEIVED, - isFirebaseMessaging: true, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - await messageEventListener( - new MessageEvent('message', { data: internalPayload }) - ); - - expect(onMessageSpy).to.have.been.calledOnceWith({ - notification: { title: 'hello', body: 'world' }, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }); - expect(logEventSpy).not.to.have.been.called; - }); - - it('does not call onMessage callback when it receives a NOTIFICATION_CLICKED message', async () => { - const internalPayload: MessagePayloadInternal = { - notification: { title: 'hello', body: 'world' }, - messageType: MessageType.NOTIFICATION_CLICKED, - isFirebaseMessaging: true, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - await messageEventListener( - new MessageEvent('message', { data: internalPayload }) - ); - - expect(onMessageSpy).not.to.have.been.called; - expect(logEventSpy).not.to.have.been.called; - }); - - it('calls analytics.logEvent if the message has analytics enabled for PUSH_RECEIVED', async () => { - const internalPayload: MessagePayloadInternal = { - notification: { title: 'hello', body: 'world' }, - data: { - [CONSOLE_CAMPAIGN_ID]: '123456', - [CONSOLE_CAMPAIGN_NAME]: 'Campaign Name', - [CONSOLE_CAMPAIGN_TIME]: '1234567890', - [CONSOLE_CAMPAIGN_ANALYTICS_ENABLED]: '1' - }, - messageType: MessageType.PUSH_RECEIVED, - isFirebaseMessaging: true, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - await messageEventListener( - new MessageEvent('message', { data: internalPayload }) - ); - - expect(onMessageSpy).to.have.been.calledOnceWith({ - notification: { title: 'hello', body: 'world' }, - data: { - [CONSOLE_CAMPAIGN_ID]: '123456', - [CONSOLE_CAMPAIGN_NAME]: 'Campaign Name', - [CONSOLE_CAMPAIGN_TIME]: '1234567890', - [CONSOLE_CAMPAIGN_ANALYTICS_ENABLED]: '1' - }, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }); - expect(logEventSpy).to.have.been.calledOnceWith( - 'notification_foreground', - { - /* eslint-disable camelcase */ - message_id: '123456', - message_name: 'Campaign Name', - message_time: '1234567890', - message_device_time: clock.now - /* eslint-enable camelcase */ - } - ); - }); - - it('calls analytics.logEvent if the message has analytics enabled for NOTIFICATION_CLICKED', async () => { - const internalPayload: MessagePayloadInternal = { - notification: { title: 'hello', body: 'world' }, - data: { - [CONSOLE_CAMPAIGN_ID]: '123456', - [CONSOLE_CAMPAIGN_NAME]: 'Campaign Name', - [CONSOLE_CAMPAIGN_TIME]: '1234567890', - [CONSOLE_CAMPAIGN_ANALYTICS_ENABLED]: '1' - }, - messageType: MessageType.NOTIFICATION_CLICKED, - isFirebaseMessaging: true, - from: 'from', - // eslint-disable-next-line camelcase - collapse_key: 'collapse', - // eslint-disable-next-line camelcase - fcm_message_id: 'mid' - }; - - await messageEventListener( - new MessageEvent('message', { data: internalPayload }) - ); - - expect(onMessageSpy).not.to.have.been.called; - expect(logEventSpy).to.have.been.calledOnceWith('notification_open', { - /* eslint-disable camelcase */ - message_id: '123456', - message_name: 'Campaign Name', - message_time: '1234567890', - message_device_time: clock.now - /* eslint-enable camelcase */ - }); - }); - }); - - describe('onTokenRefresh', () => { - it('returns an unsubscribe function that does nothing', () => { - const unsubscribe = windowController.onTokenRefresh(); - expect(unsubscribe).not.to.throw; - }); - }); -}); diff --git a/packages/messaging/src/controllers/window-controller.ts b/packages/messaging/src/controllers/window-controller.ts deleted file mode 100644 index 0ac24de9fe4..00000000000 --- a/packages/messaging/src/controllers/window-controller.ts +++ /dev/null @@ -1,298 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - CONSOLE_CAMPAIGN_ANALYTICS_ENABLED, - CONSOLE_CAMPAIGN_ID, - CONSOLE_CAMPAIGN_NAME, - CONSOLE_CAMPAIGN_TIME, - DEFAULT_SW_PATH, - DEFAULT_SW_SCOPE, - DEFAULT_VAPID_KEY -} from '../util/constants'; -import { - ConsoleMessageData, - MessagePayloadInternal, - MessageType -} from '../interfaces/internal-message-payload'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { NextFn, Observer, Unsubscribe } from '@firebase/util'; -import { deleteToken, getToken } from '../core/token-management'; - -import { FirebaseApp } from '@firebase/app-types'; -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; -import { FirebaseMessaging } from '@firebase/messaging-types'; -import { FirebaseService } from '@firebase/app-types/private'; -import { isConsoleMessage } from '../helpers/is-console-message'; - -export class WindowController implements FirebaseMessaging, FirebaseService { - private vapidKey: string | null = null; - private swRegistration?: ServiceWorkerRegistration; - private onMessageCallback: NextFn | Observer | null = null; - - constructor( - private readonly firebaseDependencies: FirebaseInternalDependencies - ) { - navigator.serviceWorker.addEventListener('message', e => - this.messageEventListener(e) - ); - } - - get app(): FirebaseApp { - return this.firebaseDependencies.app; - } - - private async messageEventListener(event: MessageEvent): Promise { - const internalPayload = event.data as MessagePayloadInternal; - - if (!internalPayload.isFirebaseMessaging) { - return; - } - - // onMessageCallback is either a function or observer/subscriber. - // TODO: in the modularization release, have onMessage handle type MessagePayload as supposed to - // the legacy payload where some fields are in snake cases. - if ( - this.onMessageCallback && - internalPayload.messageType === MessageType.PUSH_RECEIVED - ) { - if (typeof this.onMessageCallback === 'function') { - this.onMessageCallback( - stripInternalFields(Object.assign({}, internalPayload)) - ); - } else { - this.onMessageCallback.next(Object.assign({}, internalPayload)); - } - } - - const dataPayload = internalPayload.data; - - if ( - isConsoleMessage(dataPayload) && - dataPayload[CONSOLE_CAMPAIGN_ANALYTICS_ENABLED] === '1' - ) { - await this.logEvent(internalPayload.messageType!, dataPayload); - } - } - - getVapidKey(): string | null { - return this.vapidKey; - } - - getSwReg(): ServiceWorkerRegistration | undefined { - return this.swRegistration; - } - - async getToken(options?: { - vapidKey?: string; - serviceWorkerRegistration?: ServiceWorkerRegistration; - }): Promise { - if (Notification.permission === 'default') { - await Notification.requestPermission(); - } - - if (Notification.permission !== 'granted') { - throw ERROR_FACTORY.create(ErrorCode.PERMISSION_BLOCKED); - } - - await this.updateVapidKey(options?.vapidKey); - await this.updateSwReg(options?.serviceWorkerRegistration); - - return getToken( - this.firebaseDependencies, - this.swRegistration!, - this.vapidKey! - ); - } - - async updateVapidKey(vapidKey?: string | undefined): Promise { - if (!!vapidKey) { - this.vapidKey = vapidKey; - } else if (!this.vapidKey) { - this.vapidKey = DEFAULT_VAPID_KEY; - } - } - - async updateSwReg( - swRegistration?: ServiceWorkerRegistration | undefined - ): Promise { - if (!swRegistration && !this.swRegistration) { - await this.registerDefaultSw(); - } - - if (!swRegistration && !!this.swRegistration) { - return; - } - - if (!(swRegistration instanceof ServiceWorkerRegistration)) { - throw ERROR_FACTORY.create(ErrorCode.INVALID_SW_REGISTRATION); - } - - this.swRegistration = swRegistration; - } - - private async registerDefaultSw(): Promise { - try { - this.swRegistration = await navigator.serviceWorker.register( - DEFAULT_SW_PATH, - { - scope: DEFAULT_SW_SCOPE - } - ); - - // The timing when browser updates sw when sw has an update is unreliable by my experiment. It - // leads to version conflict when the SDK upgrades to a newer version in the main page, but sw - // is stuck with the old version. For example, - // https://github.com/firebase/firebase-js-sdk/issues/2590 The following line reliably updates - // sw if there was an update. - this.swRegistration.update().catch(() => { - /* it is non blocking and we don't care if it failed */ - }); - } catch (e) { - throw ERROR_FACTORY.create(ErrorCode.FAILED_DEFAULT_REGISTRATION, { - browserErrorMessage: e.message - }); - } - } - - async deleteToken(): Promise { - if (!this.swRegistration) { - await this.registerDefaultSw(); - } - - return deleteToken(this.firebaseDependencies, this.swRegistration!); - } - - /** - * Request permission if it is not currently granted. - * - * @return Resolves if the permission was granted, rejects otherwise. - * - * @deprecated Use Notification.requestPermission() instead. - * https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission - */ - async requestPermission(): Promise { - if (Notification.permission === 'granted') { - return; - } - - const permissionResult = await Notification.requestPermission(); - if (permissionResult === 'granted') { - return; - } else if (permissionResult === 'denied') { - throw ERROR_FACTORY.create(ErrorCode.PERMISSION_BLOCKED); - } else { - throw ERROR_FACTORY.create(ErrorCode.PERMISSION_DEFAULT); - } - } - - /** - * @deprecated. Use getToken(options?: {vapidKey?: string; serviceWorkerRegistration?: - * ServiceWorkerRegistration;}): Promise instead. - */ - usePublicVapidKey(vapidKey: string): void { - if (this.vapidKey !== null) { - throw ERROR_FACTORY.create(ErrorCode.USE_VAPID_KEY_AFTER_GET_TOKEN); - } - - if (typeof vapidKey !== 'string' || vapidKey.length === 0) { - throw ERROR_FACTORY.create(ErrorCode.INVALID_VAPID_KEY); - } - - this.vapidKey = vapidKey; - } - - /** - * @deprecated. Use getToken(options?: {vapidKey?: string; serviceWorkerRegistration?: - * ServiceWorkerRegistration;}): Promise instead. - */ - useServiceWorker(swRegistration: ServiceWorkerRegistration): void { - if (!(swRegistration instanceof ServiceWorkerRegistration)) { - throw ERROR_FACTORY.create(ErrorCode.INVALID_SW_REGISTRATION); - } - - if (this.swRegistration) { - throw ERROR_FACTORY.create(ErrorCode.USE_SW_AFTER_GET_TOKEN); - } - - this.swRegistration = swRegistration; - } - - /** - * @param nextOrObserver An observer object or a function triggered on message. - * - * @return The unsubscribe function for the observer. - */ - onMessage(nextOrObserver: NextFn | Observer): Unsubscribe { - this.onMessageCallback = nextOrObserver; - - return () => { - this.onMessageCallback = null; - }; - } - - setBackgroundMessageHandler(): void { - throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_SW); - } - - onBackgroundMessage(): Unsubscribe { - throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_SW); - } - - /** - * @deprecated No-op. It was initially designed with token rotation requests from server in mind. - * However, the plan to implement such feature was abandoned. - */ - onTokenRefresh(): Unsubscribe { - return () => {}; - } - - private async logEvent( - messageType: MessageType, - data: ConsoleMessageData - ): Promise { - const eventType = getEventType(messageType); - const analytics = await this.firebaseDependencies.analyticsProvider.get(); - analytics.logEvent(eventType, { - /* eslint-disable camelcase */ - message_id: data[CONSOLE_CAMPAIGN_ID], - message_name: data[CONSOLE_CAMPAIGN_NAME], - message_time: data[CONSOLE_CAMPAIGN_TIME], - message_device_time: Math.floor(Date.now() / 1000) - /* eslint-enable camelcase */ - }); - } -} - -function getEventType(messageType: MessageType): string { - switch (messageType) { - case MessageType.NOTIFICATION_CLICKED: - return 'notification_open'; - case MessageType.PUSH_RECEIVED: - return 'notification_foreground'; - default: - throw new Error(); - } -} - -function stripInternalFields( - internalPayload: MessagePayloadInternal -): MessagePayloadInternal { - delete internalPayload.messageType; - delete internalPayload.isFirebaseMessaging; - return internalPayload; -} diff --git a/packages/messaging/src/core/api.test.ts b/packages/messaging/src/core/api.test.ts deleted file mode 100644 index da3368191c8..00000000000 --- a/packages/messaging/src/core/api.test.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import { - ApiRequestBody, - requestDeleteToken, - requestGetToken, - requestUpdateToken -} from './api'; - -import { ENDPOINT } from '../util/constants'; -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; -import { Stub } from '../testing/sinon-types'; -import { TokenDetails } from '../interfaces/token-details'; -import { compareHeaders } from '../testing/compare-headers'; -import { expect } from 'chai'; -import { getFakeFirebaseDependencies } from '../testing/fakes/firebase-dependencies'; -import { getFakeTokenDetails } from '../testing/fakes/token-details'; -import { stub } from 'sinon'; - -describe('API', () => { - let tokenDetails: TokenDetails; - let firebaseDependencies: FirebaseInternalDependencies; - let fetchStub: Stub; - - beforeEach(() => { - tokenDetails = getFakeTokenDetails(); - firebaseDependencies = getFakeFirebaseDependencies(); - fetchStub = stub(self, 'fetch'); - }); - - describe('getToken', () => { - it('calls the createRegistration server API with correct parameters', async () => { - fetchStub.resolves( - new Response(JSON.stringify({ token: 'fcm-token-from-server' })) - ); - - const response = await requestGetToken( - firebaseDependencies, - tokenDetails.subscriptionOptions! - ); - - const expectedHeaders = new Headers({ - 'Content-Type': 'application/json', - Accept: 'application/json', - 'x-goog-api-key': 'apiKey', - 'x-goog-firebase-installations-auth': `FIS authToken` - }); - const expectedBody: ApiRequestBody = { - web: { - endpoint: 'https://example.org', - auth: 'YXV0aC12YWx1ZQ', - p256dh: 'cDI1Ni12YWx1ZQ', - applicationPubKey: 'dmFwaWQta2V5LXZhbHVl' - } - }; - const expectedRequest: RequestInit = { - method: 'POST', - headers: expectedHeaders, - body: JSON.stringify(expectedBody) - }; - const expectedEndpoint = `${ENDPOINT}/projects/projectId/registrations`; - - expect(response).to.equal('fcm-token-from-server'); - expect(fetchStub).to.be.calledOnceWith(expectedEndpoint, expectedRequest); - const actualHeaders = fetchStub.lastCall.lastArg.headers; - compareHeaders(expectedHeaders, actualHeaders); - }); - - it('throws if there is a problem with the response', async () => { - fetchStub.rejects(new Error('Fetch failed')); - await expect( - requestGetToken(firebaseDependencies, tokenDetails.subscriptionOptions!) - ).to.be.rejectedWith('Fetch failed'); - - fetchStub.resolves( - new Response(JSON.stringify({ error: { message: 'error message' } })) - ); - await expect( - requestGetToken(firebaseDependencies, tokenDetails.subscriptionOptions!) - ).to.be.rejectedWith('messaging/token-subscribe-failed'); - - fetchStub.resolves( - new Response( - JSON.stringify({ - /* no token */ - }) - ) - ); - await expect( - requestGetToken(firebaseDependencies, tokenDetails.subscriptionOptions!) - ).to.be.rejectedWith('messaging/token-subscribe-no-token'); - }); - }); - - describe('updateToken', () => { - it('calls the updateRegistration server API with correct parameters', async () => { - fetchStub.resolves( - new Response(JSON.stringify({ token: 'fcm-token-from-server' })) - ); - - const response = await requestUpdateToken( - firebaseDependencies, - tokenDetails - ); - - const expectedHeaders = new Headers({ - 'Content-Type': 'application/json', - Accept: 'application/json', - 'x-goog-api-key': 'apiKey', - 'x-goog-firebase-installations-auth': `FIS authToken` - }); - const expectedBody: ApiRequestBody = { - web: { - endpoint: 'https://example.org', - auth: 'YXV0aC12YWx1ZQ', - p256dh: 'cDI1Ni12YWx1ZQ', - applicationPubKey: 'dmFwaWQta2V5LXZhbHVl' - } - }; - const expectedRequest: RequestInit = { - method: 'PATCH', - headers: expectedHeaders, - body: JSON.stringify(expectedBody) - }; - const expectedEndpoint = `${ENDPOINT}/projects/projectId/registrations/token-value`; - - expect(response).to.equal('fcm-token-from-server'); - expect(fetchStub).to.be.calledOnceWith(expectedEndpoint, expectedRequest); - const actualHeaders = fetchStub.lastCall.lastArg.headers; - compareHeaders(expectedHeaders, actualHeaders); - }); - - it('throws if there is a problem with the response', async () => { - fetchStub.rejects(new Error('Fetch failed')); - await expect( - requestUpdateToken(firebaseDependencies, tokenDetails) - ).to.be.rejectedWith('Fetch failed'); - - fetchStub.resolves( - new Response(JSON.stringify({ error: { message: 'error message' } })) - ); - await expect( - requestUpdateToken(firebaseDependencies, tokenDetails) - ).to.be.rejectedWith('messaging/token-update-failed'); - - fetchStub.resolves( - new Response( - JSON.stringify({ - /* no token */ - }) - ) - ); - await expect( - requestUpdateToken(firebaseDependencies, tokenDetails) - ).to.be.rejectedWith('messaging/token-update-no-token'); - }); - }); - - describe('deleteToken', () => { - it('calls the deleteRegistration server API with correct parameters', async () => { - fetchStub.resolves(new Response(JSON.stringify({}))); - - const response = await requestDeleteToken( - firebaseDependencies, - tokenDetails.token - ); - - const expectedHeaders = new Headers({ - 'Content-Type': 'application/json', - Accept: 'application/json', - 'x-goog-api-key': 'apiKey', - 'x-goog-firebase-installations-auth': `FIS authToken` - }); - const expectedRequest: RequestInit = { - method: 'DELETE', - headers: expectedHeaders - }; - const expectedEndpoint = `${ENDPOINT}/projects/projectId/registrations/token-value`; - - expect(response).to.be.undefined; - expect(fetchStub).to.be.calledOnceWith(expectedEndpoint, expectedRequest); - const actualHeaders = fetchStub.lastCall.lastArg.headers; - compareHeaders(expectedHeaders, actualHeaders); - }); - - it('throws if there is a problem with the response', async () => { - fetchStub.rejects(new Error('Fetch failed')); - await expect( - requestDeleteToken(firebaseDependencies, tokenDetails.token) - ).to.be.rejectedWith('Fetch failed'); - - fetchStub.resolves( - new Response(JSON.stringify({ error: { message: 'error message' } })) - ); - await expect( - requestDeleteToken(firebaseDependencies, tokenDetails.token) - ).to.be.rejectedWith('messaging/token-unsubscribe-failed'); - }); - }); -}); diff --git a/packages/messaging/src/core/api.ts b/packages/messaging/src/core/api.ts deleted file mode 100644 index b93f8623601..00000000000 --- a/packages/messaging/src/core/api.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DEFAULT_VAPID_KEY, ENDPOINT } from '../util/constants'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { SubscriptionOptions, TokenDetails } from '../interfaces/token-details'; - -import { AppConfig } from '../interfaces/app-config'; -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; - -export interface ApiResponse { - token?: string; - error?: { message: string }; -} - -export interface ApiRequestBody { - web: { - endpoint: string; - p256dh: string; - auth: string; - applicationPubKey?: string; - }; -} - -export async function requestGetToken( - firebaseDependencies: FirebaseInternalDependencies, - subscriptionOptions: SubscriptionOptions -): Promise { - const headers = await getHeaders(firebaseDependencies); - const body = getBody(subscriptionOptions); - - const subscribeOptions = { - method: 'POST', - headers, - body: JSON.stringify(body) - }; - - let responseData: ApiResponse; - try { - const response = await fetch( - getEndpoint(firebaseDependencies.appConfig), - subscribeOptions - ); - responseData = await response.json(); - } catch (err) { - throw ERROR_FACTORY.create(ErrorCode.TOKEN_SUBSCRIBE_FAILED, { - errorInfo: err - }); - } - - if (responseData.error) { - const message = responseData.error.message; - throw ERROR_FACTORY.create(ErrorCode.TOKEN_SUBSCRIBE_FAILED, { - errorInfo: message - }); - } - - if (!responseData.token) { - throw ERROR_FACTORY.create(ErrorCode.TOKEN_SUBSCRIBE_NO_TOKEN); - } - - return responseData.token; -} - -export async function requestUpdateToken( - firebaseDependencies: FirebaseInternalDependencies, - tokenDetails: TokenDetails -): Promise { - const headers = await getHeaders(firebaseDependencies); - const body = getBody(tokenDetails.subscriptionOptions!); - - const updateOptions = { - method: 'PATCH', - headers, - body: JSON.stringify(body) - }; - - let responseData: ApiResponse; - try { - const response = await fetch( - `${getEndpoint(firebaseDependencies.appConfig)}/${tokenDetails.token}`, - updateOptions - ); - responseData = await response.json(); - } catch (err) { - throw ERROR_FACTORY.create(ErrorCode.TOKEN_UPDATE_FAILED, { - errorInfo: err - }); - } - - if (responseData.error) { - const message = responseData.error.message; - throw ERROR_FACTORY.create(ErrorCode.TOKEN_UPDATE_FAILED, { - errorInfo: message - }); - } - - if (!responseData.token) { - throw ERROR_FACTORY.create(ErrorCode.TOKEN_UPDATE_NO_TOKEN); - } - - return responseData.token; -} - -export async function requestDeleteToken( - firebaseDependencies: FirebaseInternalDependencies, - token: string -): Promise { - const headers = await getHeaders(firebaseDependencies); - - const unsubscribeOptions = { - method: 'DELETE', - headers - }; - - try { - const response = await fetch( - `${getEndpoint(firebaseDependencies.appConfig)}/${token}`, - unsubscribeOptions - ); - const responseData: ApiResponse = await response.json(); - if (responseData.error) { - const message = responseData.error.message; - throw ERROR_FACTORY.create(ErrorCode.TOKEN_UNSUBSCRIBE_FAILED, { - errorInfo: message - }); - } - } catch (err) { - throw ERROR_FACTORY.create(ErrorCode.TOKEN_UNSUBSCRIBE_FAILED, { - errorInfo: err - }); - } -} - -function getEndpoint({ projectId }: AppConfig): string { - return `${ENDPOINT}/projects/${projectId!}/registrations`; -} - -async function getHeaders({ - appConfig, - installations -}: FirebaseInternalDependencies): Promise { - const authToken = await installations.getToken(); - - return new Headers({ - 'Content-Type': 'application/json', - Accept: 'application/json', - 'x-goog-api-key': appConfig.apiKey!, - 'x-goog-firebase-installations-auth': `FIS ${authToken}` - }); -} - -function getBody({ - p256dh, - auth, - endpoint, - vapidKey -}: SubscriptionOptions): ApiRequestBody { - const body: ApiRequestBody = { - web: { - endpoint, - auth, - p256dh - } - }; - - if (vapidKey !== DEFAULT_VAPID_KEY) { - body.web.applicationPubKey = vapidKey; - } - - return body; -} diff --git a/packages/messaging/src/core/token-management.test.ts b/packages/messaging/src/core/token-management.test.ts deleted file mode 100644 index db9f3e39268..00000000000 --- a/packages/messaging/src/core/token-management.test.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import * as apiModule from './api'; - -import { SubscriptionOptions, TokenDetails } from '../interfaces/token-details'; -import { dbGet, dbSet } from '../helpers/idb-manager'; -import { deleteToken, getToken } from './token-management'; -import { spy, stub, useFakeTimers } from 'sinon'; - -import { DEFAULT_VAPID_KEY } from '../util/constants'; -import { ErrorCode } from '../util/errors'; -import { FakeServiceWorkerRegistration } from '../testing/fakes/service-worker'; -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; -import { Stub } from '../testing/sinon-types'; -import { arrayToBase64 } from '../helpers/array-base64-translator'; -import { expect } from 'chai'; -import { getFakeFirebaseDependencies } from '../testing/fakes/firebase-dependencies'; -import { getFakeTokenDetails } from '../testing/fakes/token-details'; - -describe('Token Management', () => { - let tokenDetails: TokenDetails; - let firebaseDependencies: FirebaseInternalDependencies; - let swRegistration: FakeServiceWorkerRegistration; - let requestGetTokenStub: Stub; - let requestUpdateTokenStub: Stub; - let requestDeleteTokenStub: Stub; - - beforeEach(() => { - useFakeTimers({ now: 1234567890 }); - - tokenDetails = getFakeTokenDetails(); - firebaseDependencies = getFakeFirebaseDependencies(); - swRegistration = new FakeServiceWorkerRegistration(); - - requestGetTokenStub = stub(apiModule, 'requestGetToken').resolves( - 'token-from-server' // new token. - ); - requestUpdateTokenStub = stub(apiModule, 'requestUpdateToken').resolves( - tokenDetails.token // same as current token. - ); - requestDeleteTokenStub = stub(apiModule, 'requestDeleteToken').resolves(); - }); - - describe('getToken', () => { - it("throws if notification permission isn't granted", async () => { - stub(Notification, 'permission').value('denied'); - - try { - await getToken(firebaseDependencies, swRegistration, DEFAULT_VAPID_KEY); - throw new Error('should have thrown'); - } catch (err) { - expect(err.code).to.equal(`messaging/${ErrorCode.PERMISSION_BLOCKED}`); - } - - expect(requestGetTokenStub).not.to.have.been.called; - expect(requestUpdateTokenStub).not.to.have.been.called; - expect(requestDeleteTokenStub).not.to.have.been.called; - }); - - it('gets a new token if there is none', async () => { - stub(Notification, 'permission').value('granted'); - - const token = await getToken( - firebaseDependencies, - swRegistration, - tokenDetails.subscriptionOptions!.vapidKey - ); - - expect(token).to.equal('token-from-server'); - expect(requestGetTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - tokenDetails.subscriptionOptions - ); - expect(requestUpdateTokenStub).not.to.have.been.called; - expect(requestDeleteTokenStub).not.to.have.been.called; - - const tokenFromDb = await dbGet(firebaseDependencies); - expect(token).to.equal(tokenFromDb!.token); - expect(tokenFromDb).to.deep.equal({ - ...tokenDetails, - token: 'token-from-server' - }); - }); - - it('deletes the token and requests a new one if the token is invalid', async () => { - stub(Notification, 'permission').value('granted'); - - await dbSet(firebaseDependencies, tokenDetails); - - // Change the auth in the Push subscription, invalidating the token. - const subscription = await swRegistration.pushManager.subscribe(); - subscription.auth = 'different-auth'; - const newAuth = arrayToBase64(subscription.getKey('auth')); - - const token = await getToken( - firebaseDependencies, - swRegistration, - tokenDetails.subscriptionOptions!.vapidKey - ); - - const expectedSubscriptionOptions: SubscriptionOptions = { - ...tokenDetails.subscriptionOptions!, - auth: newAuth - }; - - expect(token).to.equal('token-from-server'); - expect(requestGetTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - expectedSubscriptionOptions - ); - expect(requestUpdateTokenStub).not.to.have.been.called; - expect(requestDeleteTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - tokenDetails.token - ); - - const tokenFromDb = await dbGet(firebaseDependencies); - expect(token).to.equal(tokenFromDb!.token); - expect(tokenFromDb).to.deep.equal({ - ...tokenDetails, - token, - subscriptionOptions: expectedSubscriptionOptions - }); - }); - - it('deletes the token and requests a new one if the VAPID key changes', async () => { - stub(Notification, 'permission').value('granted'); - - await dbSet(firebaseDependencies, tokenDetails); - - const token = await getToken( - firebaseDependencies, - swRegistration, - 'some-other-vapid-key' - ); - - const expectedSubscriptionOptions: SubscriptionOptions = { - ...tokenDetails.subscriptionOptions!, - vapidKey: 'some-other-vapid-key' - }; - - expect(token).to.equal('token-from-server'); - expect(requestGetTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - expectedSubscriptionOptions - ); - expect(requestUpdateTokenStub).not.to.have.been.called; - expect(requestDeleteTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - tokenDetails.token - ); - - const tokenFromDb = await dbGet(firebaseDependencies); - expect(token).to.equal(tokenFromDb!.token); - expect(tokenFromDb).to.deep.equal({ - ...tokenDetails, - token, - subscriptionOptions: expectedSubscriptionOptions - }); - }); - - it('updates the token if it was last updated more than a week ago', async () => { - stub(Notification, 'permission').value('granted'); - - // Change create time to be older than a week. - tokenDetails.createTime = Date.now() - 8 * 24 * 60 * 60 * 1000; // 8 days - - await dbSet(firebaseDependencies, tokenDetails); - - const token = await getToken( - firebaseDependencies, - swRegistration, - tokenDetails.subscriptionOptions!.vapidKey - ); - const expectedTokenDetails: TokenDetails = { - ...tokenDetails, - createTime: Date.now() - }; - - expect(token).to.equal(tokenDetails.token); // Same token. - expect(requestGetTokenStub).not.to.have.been.called; - expect(requestUpdateTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - expectedTokenDetails - ); - expect(requestDeleteTokenStub).not.to.have.been.called; - - const tokenFromDb = await dbGet(firebaseDependencies); - expect(token).to.equal(tokenFromDb!.token); - expect(tokenFromDb).to.deep.equal(expectedTokenDetails); - }); - - it('deletes the token if the update fails', async () => { - stub(Notification, 'permission').value('granted'); - - // Change create time to be older than a week. - tokenDetails.createTime = Date.now() - 8 * 24 * 60 * 60 * 1000; // 8 days - - await dbSet(firebaseDependencies, tokenDetails); - - requestUpdateTokenStub.rejects(new Error('Update failed.')); - - await expect( - getToken( - firebaseDependencies, - swRegistration, - tokenDetails.subscriptionOptions!.vapidKey - ) - ).to.be.rejectedWith('Update failed.'); - - const expectedTokenDetails: TokenDetails = { - ...tokenDetails, - createTime: Date.now() - }; - - expect(requestGetTokenStub).not.to.have.been.called; - expect(requestUpdateTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - expectedTokenDetails - ); - expect(requestDeleteTokenStub).to.have.been.calledOnceWith( - firebaseDependencies, - tokenDetails.token - ); - - const tokenFromDb = await dbGet(firebaseDependencies); - expect(tokenFromDb).to.be.undefined; - }); - - it('returns the token if it is valid', async () => { - stub(Notification, 'permission').value('granted'); - - await dbSet(firebaseDependencies, tokenDetails); - - const token = await getToken( - firebaseDependencies, - swRegistration, - tokenDetails.subscriptionOptions!.vapidKey - ); - - expect(token).to.equal(tokenDetails.token); - expect(requestGetTokenStub).not.to.have.been.called; - expect(requestUpdateTokenStub).not.to.have.been.called; - expect(requestDeleteTokenStub).not.to.have.been.called; - - const tokenFromDb = await dbGet(firebaseDependencies); - expect(tokenFromDb).to.deep.equal(tokenDetails); - }); - }); - - describe('deleteToken', () => { - it('returns if there is no token in the db', async () => { - await deleteToken(firebaseDependencies, swRegistration); - - expect(requestGetTokenStub).not.to.have.been.called; - expect(requestUpdateTokenStub).not.to.have.been.called; - expect(requestDeleteTokenStub).not.to.have.been.called; - }); - - it('removes token from the db, calls requestDeleteToken and unsubscribes the push subscription', async () => { - const unsubscribeSpy = spy( - await swRegistration.pushManager.subscribe(), - 'unsubscribe' - ); - await dbSet(firebaseDependencies, tokenDetails); - - await deleteToken(firebaseDependencies, swRegistration); - - expect(await dbGet(firebaseDependencies)).to.be.undefined; - expect(requestGetTokenStub).not.to.have.been.called; - expect(requestUpdateTokenStub).not.to.have.been.called; - expect(requestDeleteTokenStub).not.to.have.been.calledOnceWith( - firebaseDependencies, - tokenDetails - ); - expect(unsubscribeSpy).to.have.been.called; - }); - }); -}); diff --git a/packages/messaging/src/core/token-management.ts b/packages/messaging/src/core/token-management.ts deleted file mode 100644 index b314c89fc77..00000000000 --- a/packages/messaging/src/core/token-management.ts +++ /dev/null @@ -1,184 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { SubscriptionOptions, TokenDetails } from '../interfaces/token-details'; -import { - arrayToBase64, - base64ToArray -} from '../helpers/array-base64-translator'; -import { dbGet, dbRemove, dbSet } from '../helpers/idb-manager'; -import { requestDeleteToken, requestGetToken, requestUpdateToken } from './api'; - -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; - -/** UpdateRegistration will be called once every week. */ -const TOKEN_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days - -export async function getToken( - firebaseDependencies: FirebaseInternalDependencies, - swRegistration: ServiceWorkerRegistration, - vapidKey: string -): Promise { - if (Notification.permission !== 'granted') { - throw ERROR_FACTORY.create(ErrorCode.PERMISSION_BLOCKED); - } - - // If a PushSubscription exists it's returned, otherwise a new subscription is generated and - // returned. - const pushSubscription = await getPushSubscription(swRegistration, vapidKey); - const tokenDetails = await dbGet(firebaseDependencies); - - const subscriptionOptions: SubscriptionOptions = { - vapidKey, - swScope: swRegistration.scope, - endpoint: pushSubscription.endpoint, - auth: arrayToBase64(pushSubscription.getKey('auth')!), - p256dh: arrayToBase64(pushSubscription.getKey('p256dh')!) - }; - - if (!tokenDetails) { - // No token, get a new one. - return getNewToken(firebaseDependencies, subscriptionOptions); - } else if ( - !isTokenValid(tokenDetails.subscriptionOptions!, subscriptionOptions) - ) { - // Invalid token, get a new one. - try { - await requestDeleteToken(firebaseDependencies, tokenDetails.token); - } catch (e) { - // Suppress errors because of #2364 - console.warn(e); - } - - return getNewToken(firebaseDependencies, subscriptionOptions); - } else if (Date.now() >= tokenDetails.createTime + TOKEN_EXPIRATION_MS) { - // Weekly token refresh - return updateToken( - { - token: tokenDetails.token, - createTime: Date.now(), - subscriptionOptions - }, - firebaseDependencies, - swRegistration - ); - } else { - // Valid token, nothing to do. - return tokenDetails.token; - } -} - -/** - * This method deletes the token from the database, unsubscribes the token from FCM, and unregisters - * the push subscription if it exists. - */ -export async function deleteToken( - firebaseDependencies: FirebaseInternalDependencies, - swRegistration: ServiceWorkerRegistration -): Promise { - const tokenDetails = await dbGet(firebaseDependencies); - if (tokenDetails) { - await requestDeleteToken(firebaseDependencies, tokenDetails.token); - await dbRemove(firebaseDependencies); - } - - // Unsubscribe from the push subscription. - const pushSubscription = await swRegistration.pushManager.getSubscription(); - if (pushSubscription) { - return pushSubscription.unsubscribe(); - } - - // If there's no SW, consider it a success. - return true; -} - -async function updateToken( - tokenDetails: TokenDetails, - firebaseDependencies: FirebaseInternalDependencies, - swRegistration: ServiceWorkerRegistration -): Promise { - try { - const updatedToken = await requestUpdateToken( - firebaseDependencies, - tokenDetails - ); - - const updatedTokenDetails: TokenDetails = { - ...tokenDetails, - token: updatedToken, - createTime: Date.now() - }; - - await dbSet(firebaseDependencies, updatedTokenDetails); - return updatedToken; - } catch (e) { - await deleteToken(firebaseDependencies, swRegistration); - throw e; - } -} - -async function getNewToken( - firebaseDependencies: FirebaseInternalDependencies, - subscriptionOptions: SubscriptionOptions -): Promise { - const token = await requestGetToken( - firebaseDependencies, - subscriptionOptions - ); - const tokenDetails: TokenDetails = { - token, - createTime: Date.now(), - subscriptionOptions - }; - await dbSet(firebaseDependencies, tokenDetails); - return tokenDetails.token; -} - -/** - * Gets a PushSubscription for the current user. - */ -async function getPushSubscription( - swRegistration: ServiceWorkerRegistration, - vapidKey: string -): Promise { - const subscription = await swRegistration.pushManager.getSubscription(); - if (subscription) { - return subscription; - } - return swRegistration.pushManager.subscribe({ - userVisibleOnly: true, - // Chrome <= 75 doesn't support base64-encoded VAPID key. For backward compatibility, VAPID key - // submitted to pushManager#subscribe must be of type Uint8Array. - applicationServerKey: base64ToArray(vapidKey) - }); -} - -/** - * Checks if the saved tokenDetails object matches the configuration provided. - */ -function isTokenValid( - dbOptions: SubscriptionOptions, - currentOptions: SubscriptionOptions -): boolean { - const isVapidKeyEqual = currentOptions.vapidKey === dbOptions.vapidKey; - const isEndpointEqual = currentOptions.endpoint === dbOptions.endpoint; - const isAuthEqual = currentOptions.auth === dbOptions.auth; - const isP256dhEqual = currentOptions.p256dh === dbOptions.p256dh; - - return isVapidKeyEqual && isEndpointEqual && isAuthEqual && isP256dhEqual; -} diff --git a/packages/messaging/src/helpers/externalizePayload.test.ts b/packages/messaging/src/helpers/externalizePayload.test.ts index a209006b85b..d68520c055a 100644 --- a/packages/messaging/src/helpers/externalizePayload.test.ts +++ b/packages/messaging/src/helpers/externalizePayload.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { MessagePayload } from '@firebase/messaging-types'; +import { MessagePayload } from '../interfaces/public-types'; import { MessagePayloadInternal } from '../interfaces/internal-message-payload'; import { expect } from 'chai'; import { externalizePayload } from './externalizePayload'; @@ -35,13 +35,13 @@ describe('externalizePayload', () => { fcm_message_id: 'mid' }; - const expected: MessagePayload = { + const payload: MessagePayload = { notification: { title: 'title', body: 'body', image: 'image' }, from: 'from', collapseKey: 'collapse', messageId: 'mid' }; - expect(externalizePayload(internalPayload)).to.deep.equal(expected); + expect(externalizePayload(internalPayload)).to.deep.equal(payload); }); it('externalizes internalMessage with only data payload', () => { @@ -58,13 +58,13 @@ describe('externalizePayload', () => { fcm_message_id: 'mid' }; - const expected: MessagePayload = { + const payload: MessagePayload = { data: { foo: 'foo', bar: 'bar', baz: 'baz' }, from: 'from', collapseKey: 'collapse', messageId: 'mid' }; - expect(externalizePayload(internalPayload)).to.deep.equal(expected); + expect(externalizePayload(internalPayload)).to.deep.equal(payload); }); it('externalizes internalMessage with all three payloads', () => { @@ -91,7 +91,7 @@ describe('externalizePayload', () => { fcm_message_id: 'mid' }; - const expected: MessagePayload = { + const payload: MessagePayload = { notification: { title: 'title', body: 'body', @@ -110,6 +110,6 @@ describe('externalizePayload', () => { collapseKey: 'collapse', messageId: 'mid' }; - expect(externalizePayload(internalPayload)).to.deep.equal(expected); + expect(externalizePayload(internalPayload)).to.deep.equal(payload); }); }); diff --git a/packages/messaging/src/helpers/externalizePayload.ts b/packages/messaging/src/helpers/externalizePayload.ts index 60f93327e34..795e8ac4a63 100644 --- a/packages/messaging/src/helpers/externalizePayload.ts +++ b/packages/messaging/src/helpers/externalizePayload.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { MessagePayload } from '@firebase/messaging-types'; +import { MessagePayload } from '../interfaces/public-types'; import { MessagePayloadInternal } from '../interfaces/internal-message-payload'; export function externalizePayload( diff --git a/packages/messaging/src/helpers/extract-app-config.test.ts b/packages/messaging/src/helpers/extract-app-config.test.ts index 27d27c18aee..85836856aef 100644 --- a/packages/messaging/src/helpers/extract-app-config.test.ts +++ b/packages/messaging/src/helpers/extract-app-config.test.ts @@ -18,7 +18,7 @@ import '../testing/setup'; import { AppConfig } from '../interfaces/app-config'; -import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseApp } from '@firebase/app'; import { expect } from 'chai'; import { extractAppConfig } from './extract-app-config'; import { getFakeApp } from '../testing/fakes/firebase-dependencies'; @@ -42,14 +42,12 @@ describe('extractAppConfig', () => { ).to.throw('Missing App configuration value: "App Configuration Object"'); let firebaseApp = getFakeApp(); - // @ts-expect-error delete firebaseApp.options; expect(() => extractAppConfig(firebaseApp)).to.throw( 'Missing App configuration value: "App Configuration Object"' ); firebaseApp = getFakeApp(); - // @ts-expect-error delete firebaseApp.name; expect(() => extractAppConfig(firebaseApp)).to.throw( 'Missing App configuration value: "App Name"' diff --git a/packages/messaging/src/helpers/extract-app-config.ts b/packages/messaging/src/helpers/extract-app-config.ts index e95a45ced7e..b585dc1ece5 100644 --- a/packages/messaging/src/helpers/extract-app-config.ts +++ b/packages/messaging/src/helpers/extract-app-config.ts @@ -16,7 +16,7 @@ */ import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { FirebaseApp, FirebaseOptions } from '@firebase/app-types'; +import { FirebaseApp, FirebaseOptions } from '@firebase/app'; import { AppConfig } from '../interfaces/app-config'; import { FirebaseError } from '@firebase/util'; diff --git a/packages/messaging/src/helpers/idb-manager.test.ts b/packages/messaging/src/helpers/idb-manager.test.ts deleted file mode 100644 index b98ad933378..00000000000 --- a/packages/messaging/src/helpers/idb-manager.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import '../testing/setup'; - -import * as migrateOldDatabaseModule from './migrate-old-database'; - -import { dbGet, dbRemove, dbSet } from './idb-manager'; - -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; -import { Stub } from '../testing/sinon-types'; -import { TokenDetails } from '../interfaces/token-details'; -import { expect } from 'chai'; -import { getFakeFirebaseDependencies } from '../testing/fakes/firebase-dependencies'; -import { getFakeTokenDetails } from '../testing/fakes/token-details'; -import { stub } from 'sinon'; - -describe('idb manager', () => { - let firebaseDependencies: FirebaseInternalDependencies; - let tokenDetailsA: TokenDetails; - let tokenDetailsB: TokenDetails; - - beforeEach(() => { - firebaseDependencies = getFakeFirebaseDependencies(); - tokenDetailsA = getFakeTokenDetails(); - tokenDetailsB = getFakeTokenDetails(); - tokenDetailsA.token = 'TOKEN_A'; - tokenDetailsB.token = 'TOKEN_B'; - }); - - describe('get / set', () => { - it('sets a value and then gets the same value back', async () => { - await dbSet(firebaseDependencies, tokenDetailsA); - const value = await dbGet(firebaseDependencies); - expect(value).to.deep.equal(tokenDetailsA); - }); - - it('gets undefined for a key that does not exist', async () => { - const value = await dbGet(firebaseDependencies); - expect(value).to.be.undefined; - }); - - it('sets and gets multiple values with different keys', async () => { - const firebaseDependenciesB = getFakeFirebaseDependencies({ - appId: 'different-app-id' - }); - await dbSet(firebaseDependencies, tokenDetailsA); - await dbSet(firebaseDependenciesB, tokenDetailsB); - expect(await dbGet(firebaseDependencies)).to.deep.equal(tokenDetailsA); - expect(await dbGet(firebaseDependenciesB)).to.deep.equal(tokenDetailsB); - }); - - it('overwrites a value', async () => { - await dbSet(firebaseDependencies, tokenDetailsA); - await dbSet(firebaseDependencies, tokenDetailsB); - expect(await dbGet(firebaseDependencies)).to.deep.equal(tokenDetailsB); - }); - - describe('old DB migration', () => { - let migrateOldDatabaseStub: Stub< - typeof migrateOldDatabaseModule['migrateOldDatabase'] - >; - - beforeEach(() => { - migrateOldDatabaseStub = stub( - migrateOldDatabaseModule, - 'migrateOldDatabase' - ).resolves(tokenDetailsA); - }); - - it('gets value from old DB if there is one', async () => { - await dbGet(firebaseDependencies); - - expect(migrateOldDatabaseStub).to.have.been.calledOnceWith( - firebaseDependencies.appConfig.senderId - ); - }); - - it('does not call migrateOldDatabase a second time', async () => { - await dbGet(firebaseDependencies); - await dbGet(firebaseDependencies); - - expect(migrateOldDatabaseStub).to.have.been.calledOnceWith( - firebaseDependencies.appConfig.senderId - ); - }); - - it('does not call migrateOldDatabase if there is already a value in the DB', async () => { - await dbSet(firebaseDependencies, tokenDetailsA); - - await dbGet(firebaseDependencies); - - expect(migrateOldDatabaseStub).not.to.have.been.called; - }); - }); - }); - - describe('remove', () => { - it('deletes a key', async () => { - await dbSet(firebaseDependencies, tokenDetailsA); - await dbRemove(firebaseDependencies); - expect(await dbGet(firebaseDependencies)).to.be.undefined; - }); - - it('does not throw if key does not exist', async () => { - await dbRemove(firebaseDependencies); - expect(await dbGet(firebaseDependencies)).to.be.undefined; - }); - }); -}); diff --git a/packages/messaging/src/helpers/idb-manager.ts b/packages/messaging/src/helpers/idb-manager.ts deleted file mode 100644 index dbe957de244..00000000000 --- a/packages/messaging/src/helpers/idb-manager.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DB, deleteDb, openDb } from 'idb'; - -import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; -import { TokenDetails } from '../interfaces/token-details'; -import { migrateOldDatabase } from './migrate-old-database'; - -// Exported for tests. -export const DATABASE_NAME = 'firebase-messaging-database'; -const DATABASE_VERSION = 1; -const OBJECT_STORE_NAME = 'firebase-messaging-store'; - -let dbPromise: Promise | null = null; -function getDbPromise(): Promise { - if (!dbPromise) { - dbPromise = openDb(DATABASE_NAME, DATABASE_VERSION, upgradeDb => { - // We don't use 'break' in this switch statement, the fall-through behavior is what we want, - // because if there are multiple versions between the old version and the current version, we - // want ALL the migrations that correspond to those versions to run, not only the last one. - // eslint-disable-next-line default-case - switch (upgradeDb.oldVersion) { - case 0: - upgradeDb.createObjectStore(OBJECT_STORE_NAME); - } - }); - } - return dbPromise; -} - -/** Gets record(s) from the objectStore that match the given key. */ -export async function dbGet( - firebaseDependencies: FirebaseInternalDependencies -): Promise { - const key = getKey(firebaseDependencies); - const db = await getDbPromise(); - const tokenDetails = await db - .transaction(OBJECT_STORE_NAME) - .objectStore(OBJECT_STORE_NAME) - .get(key); - - if (tokenDetails) { - return tokenDetails; - } else { - // Check if there is a tokenDetails object in the old DB. - const oldTokenDetails = await migrateOldDatabase( - firebaseDependencies.appConfig.senderId - ); - if (oldTokenDetails) { - await dbSet(firebaseDependencies, oldTokenDetails); - return oldTokenDetails; - } - } -} - -/** Assigns or overwrites the record for the given key with the given value. */ -export async function dbSet( - firebaseDependencies: FirebaseInternalDependencies, - tokenDetails: TokenDetails -): Promise { - const key = getKey(firebaseDependencies); - const db = await getDbPromise(); - const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); - await tx.objectStore(OBJECT_STORE_NAME).put(tokenDetails, key); - await tx.complete; - return tokenDetails; -} - -/** Removes record(s) from the objectStore that match the given key. */ -export async function dbRemove( - firebaseDependencies: FirebaseInternalDependencies -): Promise { - const key = getKey(firebaseDependencies); - const db = await getDbPromise(); - const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); - await tx.objectStore(OBJECT_STORE_NAME).delete(key); - await tx.complete; -} - -/** Deletes the DB. Useful for tests. */ -export async function dbDelete(): Promise { - if (dbPromise) { - (await dbPromise).close(); - await deleteDb(DATABASE_NAME); - dbPromise = null; - } -} - -function getKey({ appConfig }: FirebaseInternalDependencies): string { - return appConfig.appId; -} diff --git a/packages-exp/messaging-exp/src/helpers/logToFirelog.test.ts b/packages/messaging/src/helpers/logToFirelog.test.ts similarity index 100% rename from packages-exp/messaging-exp/src/helpers/logToFirelog.test.ts rename to packages/messaging/src/helpers/logToFirelog.test.ts diff --git a/packages-exp/messaging-exp/src/helpers/logToFirelog.ts b/packages/messaging/src/helpers/logToFirelog.ts similarity index 100% rename from packages-exp/messaging-exp/src/helpers/logToFirelog.ts rename to packages/messaging/src/helpers/logToFirelog.ts diff --git a/packages-exp/messaging-exp/src/helpers/logToScion.ts b/packages/messaging/src/helpers/logToScion.ts similarity index 100% rename from packages-exp/messaging-exp/src/helpers/logToScion.ts rename to packages/messaging/src/helpers/logToScion.ts diff --git a/packages-exp/messaging-exp/src/helpers/register.ts b/packages/messaging/src/helpers/register.ts similarity index 59% rename from packages-exp/messaging-exp/src/helpers/register.ts rename to packages/messaging/src/helpers/register.ts index a4bf919ef48..97c7dfb6327 100644 --- a/packages-exp/messaging-exp/src/helpers/register.ts +++ b/packages/messaging/src/helpers/register.ts @@ -21,8 +21,6 @@ import { ComponentType, InstanceFactory } from '@firebase/component'; -import { ERROR_FACTORY, ErrorCode } from '../util/errors'; -import { isSwSupported, isWindowSupported } from '../api/isSupported'; import { onNotificationClick, onPush, @@ -33,18 +31,16 @@ import { GetTokenOptions } from '../interfaces/public-types'; import { MessagingInternal } from '@firebase/messaging-interop-types'; import { MessagingService } from '../messaging-service'; import { ServiceWorkerGlobalScope } from '../util/sw-types'; -import { _registerComponent } from '@firebase/app-exp'; +import { _registerComponent } from '@firebase/app'; import { getToken } from '../api/getToken'; import { messageEventListener } from '../listeners/window-listener'; -const WindowMessagingFactory: InstanceFactory<'messaging-exp'> = ( +const WindowMessagingFactory: InstanceFactory<'messaging'> = ( container: ComponentContainer ) => { - maybeThrowWindowError(); - const messaging = new MessagingService( - container.getProvider('app-exp').getImmediate(), - container.getProvider('installations-exp-internal').getImmediate(), + container.getProvider('app').getImmediate(), + container.getProvider('installations-internal').getImmediate(), container.getProvider('analytics-internal') ); @@ -58,10 +54,8 @@ const WindowMessagingFactory: InstanceFactory<'messaging-exp'> = ( const WindowMessagingInternalFactory: InstanceFactory<'messaging-internal'> = ( container: ComponentContainer ) => { - maybeThrowWindowError(); - const messaging = container - .getProvider('messaging-exp') + .getProvider('messaging') .getImmediate() as MessagingService; const messagingInternal: MessagingInternal = { @@ -71,43 +65,13 @@ const WindowMessagingInternalFactory: InstanceFactory<'messaging-internal'> = ( return messagingInternal; }; -function maybeThrowWindowError(): void { - // Conscious decision to make this async check non-blocking during the messaging instance - // initialization phase for performance consideration. An error would be thrown latter for - // developer's information. Developers can then choose to import and call `isSupported` for - // special handling. - isWindowSupported() - .then(isSupported => { - if (!isSupported) { - throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); - } - }) - .catch(_ => { - throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED); - }); -} - declare const self: ServiceWorkerGlobalScope; -const SwMessagingFactory: InstanceFactory<'messaging-exp'> = ( +const SwMessagingFactory: InstanceFactory<'messaging'> = ( container: ComponentContainer ) => { - // Conscious decision to make this async check non-blocking during the messaging instance - // initialization phase for performance consideration. An error would be thrown latter for - // developer's information. Developers can then choose to import and call `isSupported` for - // special handling. - isSwSupported() - .then(isSupported => { - if (!isSupported) { - throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); - } - }) - .catch(_ => { - throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED); - }); - const messaging = new MessagingService( - container.getProvider('app-exp').getImmediate(), - container.getProvider('installations-exp-internal').getImmediate(), + container.getProvider('app').getImmediate(), + container.getProvider('installations-internal').getImmediate(), container.getProvider('analytics-internal') ); @@ -126,7 +90,7 @@ const SwMessagingFactory: InstanceFactory<'messaging-exp'> = ( export function registerMessagingInWindow(): void { _registerComponent( - new Component('messaging-exp', WindowMessagingFactory, ComponentType.PUBLIC) + new Component('messaging', WindowMessagingFactory, ComponentType.PUBLIC) ); _registerComponent( @@ -145,6 +109,6 @@ export function registerMessagingInWindow(): void { */ export function registerMessagingInSw(): void { _registerComponent( - new Component('messaging-sw-exp', SwMessagingFactory, ComponentType.PUBLIC) + new Component('messaging-sw', SwMessagingFactory, ComponentType.PUBLIC) ); } diff --git a/packages-exp/messaging-exp/src/helpers/registerDefaultSw.ts b/packages/messaging/src/helpers/registerDefaultSw.ts similarity index 100% rename from packages-exp/messaging-exp/src/helpers/registerDefaultSw.ts rename to packages/messaging/src/helpers/registerDefaultSw.ts diff --git a/packages-exp/messaging-exp/src/helpers/updateSwReg.ts b/packages/messaging/src/helpers/updateSwReg.ts similarity index 100% rename from packages-exp/messaging-exp/src/helpers/updateSwReg.ts rename to packages/messaging/src/helpers/updateSwReg.ts diff --git a/packages-exp/messaging-exp/src/helpers/updateVapidKey.ts b/packages/messaging/src/helpers/updateVapidKey.ts similarity index 100% rename from packages-exp/messaging-exp/src/helpers/updateVapidKey.ts rename to packages/messaging/src/helpers/updateVapidKey.ts diff --git a/packages-exp/messaging-exp/src/index.sw.ts b/packages/messaging/src/index.sw.ts similarity index 90% rename from packages-exp/messaging-exp/src/index.sw.ts rename to packages/messaging/src/index.sw.ts index 17959fd17af..b5cca6f7c67 100644 --- a/packages-exp/messaging-exp/src/index.sw.ts +++ b/packages/messaging/src/index.sw.ts @@ -15,11 +15,12 @@ * limitations under the License. */ -import '@firebase/installations-exp'; +import '@firebase/installations'; import { Messaging } from './interfaces/public-types'; import { registerMessagingInSw } from './helpers/register'; +export * from './interfaces/public-types'; export { onBackgroundMessage, getMessagingInSw as getMessaging, @@ -29,7 +30,7 @@ export { isSwSupported as isSupported } from './api/isSupported'; declare module '@firebase/component' { interface NameServiceMapping { - 'messaging-sw-exp': Messaging; + 'messaging-sw': Messaging; } } diff --git a/packages/messaging/src/index.ts b/packages/messaging/src/index.ts index 4b0ba76a7c5..389e45f5ab8 100644 --- a/packages/messaging/src/index.ts +++ b/packages/messaging/src/index.ts @@ -1,3 +1,9 @@ +/** + * Firebase Cloud Messaging + * + * @packageDocumentation + */ + /** * @license * Copyright 2017 Google LLC @@ -17,118 +23,22 @@ import '@firebase/installations'; -import { - Component, - ComponentContainer, - ComponentType -} from '@firebase/component'; -import { ERROR_FACTORY, ErrorCode } from './util/errors'; -import { - FirebaseService, - _FirebaseNamespace -} from '@firebase/app-types/private'; - -import { FirebaseInternalDependencies } from './interfaces/internal-dependencies'; -import { FirebaseMessaging } from '@firebase/messaging-types'; -import { SwController } from './controllers/sw-controller'; -import { WindowController } from './controllers/window-controller'; -import { extractAppConfig } from './helpers/extract-app-config'; -import firebase from '@firebase/app'; - -const MESSAGING_NAME = 'messaging'; -function factoryMethod( - container: ComponentContainer -): FirebaseService & FirebaseMessaging { - // Dependencies. - const app = container.getProvider('app').getImmediate(); - const appConfig = extractAppConfig(app); - const installations = container.getProvider('installations').getImmediate(); - const analyticsProvider = container.getProvider('analytics-internal'); - - const firebaseDependencies: FirebaseInternalDependencies = { - app, - appConfig, - installations, - analyticsProvider - }; - - if (!isSupported()) { - throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); - } - - if (self && 'ServiceWorkerGlobalScope' in self) { - // Running in ServiceWorker context - return new SwController(firebaseDependencies); - } else { - // Assume we are in the window context. - return new WindowController(firebaseDependencies); - } -} - -const NAMESPACE_EXPORTS = { - isSupported -}; - -(firebase as _FirebaseNamespace).INTERNAL.registerComponent( - new Component( - MESSAGING_NAME, - factoryMethod, - ComponentType.PUBLIC - ).setServiceProps(NAMESPACE_EXPORTS) -); - -/** - * Define extension behavior of `registerMessaging` - */ -declare module '@firebase/app-types' { - interface FirebaseNamespace { - messaging: { - (app?: FirebaseApp): FirebaseMessaging; - isSupported(): boolean; - }; - } - interface FirebaseApp { - messaging(): FirebaseMessaging; - } -} - -function isSupported(): boolean { - if (self && 'ServiceWorkerGlobalScope' in self) { - // Running in ServiceWorker context - return isSWControllerSupported(); - } else { - // Assume we are in the window context. - return isWindowControllerSupported(); +import { Messaging } from './interfaces/public-types'; +import { registerMessagingInWindow } from './helpers/register'; + +export { + getToken, + deleteToken, + onMessage, + getMessagingInWindow as getMessaging +} from './api'; +export { isWindowSupported as isSupported } from './api/isSupported'; +export * from './interfaces/public-types'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'messaging': Messaging; } } -/** - * Checks to see if the required APIs exist. - */ -function isWindowControllerSupported(): boolean { - return ( - 'indexedDB' in window && - indexedDB !== null && - navigator.cookieEnabled && - 'serviceWorker' in navigator && - 'PushManager' in window && - 'Notification' in window && - 'fetch' in window && - ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') && - PushSubscription.prototype.hasOwnProperty('getKey') - ); -} - -/** - * Checks to see if the required APIs exist within SW Context. - */ -function isSWControllerSupported(): boolean { - return ( - 'indexedDB' in self && - indexedDB !== null && - 'PushManager' in self && - 'Notification' in self && - ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') && - PushSubscription.prototype.hasOwnProperty('getKey') - ); -} +registerMessagingInWindow(); diff --git a/packages/messaging/src/interfaces/internal-dependencies.ts b/packages/messaging/src/interfaces/internal-dependencies.ts index ecc380f2089..ce07c167129 100644 --- a/packages/messaging/src/interfaces/internal-dependencies.ts +++ b/packages/messaging/src/interfaces/internal-dependencies.ts @@ -17,13 +17,13 @@ import { AppConfig } from './app-config'; import { FirebaseAnalyticsInternalName } from '@firebase/analytics-interop-types'; -import { FirebaseApp } from '@firebase/app-types'; -import { FirebaseInstallations } from '@firebase/installations-types'; +import { FirebaseApp } from '@firebase/app'; import { Provider } from '@firebase/component'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; export interface FirebaseInternalDependencies { app: FirebaseApp; appConfig: AppConfig; - installations: FirebaseInstallations; + installations: _FirebaseInstallationsInternal; analyticsProvider: Provider; } diff --git a/packages-exp/messaging-exp/src/interfaces/logging-types.ts b/packages/messaging/src/interfaces/logging-types.ts similarity index 100% rename from packages-exp/messaging-exp/src/interfaces/logging-types.ts rename to packages/messaging/src/interfaces/logging-types.ts diff --git a/packages-exp/messaging-exp/src/interfaces/public-types.ts b/packages/messaging/src/interfaces/public-types.ts similarity index 94% rename from packages-exp/messaging-exp/src/interfaces/public-types.ts rename to packages/messaging/src/interfaces/public-types.ts index f7a8fd5ef60..382db13a640 100644 --- a/packages-exp/messaging-exp/src/interfaces/public-types.ts +++ b/packages/messaging/src/interfaces/public-types.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { FirebaseApp } from '@firebase/app'; + /** * Display notification details. They are sent through the * {@link https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#notification | Send API} @@ -131,7 +133,12 @@ export interface GetTokenOptions { * * @public */ -export interface Messaging {} +export interface Messaging { + /** + * The {@link @firebase/app#FirebaseApp} this `Messaging` instance is associated with. + */ + app: FirebaseApp; +} /** * @internal @@ -142,6 +149,6 @@ export { NextFn, Observer, Unsubscribe } from '@firebase/util'; declare module '@firebase/component' { interface NameServiceMapping { - 'messaging-exp': Messaging; + 'messaging': Messaging; } } diff --git a/packages-exp/messaging-exp/src/internals/idb-manager.test.ts b/packages/messaging/src/internals/idb-manager.test.ts similarity index 100% rename from packages-exp/messaging-exp/src/internals/idb-manager.test.ts rename to packages/messaging/src/internals/idb-manager.test.ts diff --git a/packages-exp/messaging-exp/src/internals/idb-manager.ts b/packages/messaging/src/internals/idb-manager.ts similarity index 100% rename from packages-exp/messaging-exp/src/internals/idb-manager.ts rename to packages/messaging/src/internals/idb-manager.ts diff --git a/packages-exp/messaging-exp/src/internals/requests.test.ts b/packages/messaging/src/internals/requests.test.ts similarity index 100% rename from packages-exp/messaging-exp/src/internals/requests.test.ts rename to packages/messaging/src/internals/requests.test.ts diff --git a/packages-exp/messaging-exp/src/internals/requests.ts b/packages/messaging/src/internals/requests.ts similarity index 100% rename from packages-exp/messaging-exp/src/internals/requests.ts rename to packages/messaging/src/internals/requests.ts diff --git a/packages-exp/messaging-exp/src/internals/token-manager.test.ts b/packages/messaging/src/internals/token-manager.test.ts similarity index 100% rename from packages-exp/messaging-exp/src/internals/token-manager.test.ts rename to packages/messaging/src/internals/token-manager.test.ts diff --git a/packages-exp/messaging-exp/src/internals/token-manager.ts b/packages/messaging/src/internals/token-manager.ts similarity index 100% rename from packages-exp/messaging-exp/src/internals/token-manager.ts rename to packages/messaging/src/internals/token-manager.ts diff --git a/packages-exp/messaging-exp/src/listeners/sw-listeners.test.ts b/packages/messaging/src/listeners/sw-listeners.test.ts similarity index 100% rename from packages-exp/messaging-exp/src/listeners/sw-listeners.test.ts rename to packages/messaging/src/listeners/sw-listeners.test.ts diff --git a/packages-exp/messaging-exp/src/listeners/sw-listeners.ts b/packages/messaging/src/listeners/sw-listeners.ts similarity index 100% rename from packages-exp/messaging-exp/src/listeners/sw-listeners.ts rename to packages/messaging/src/listeners/sw-listeners.ts diff --git a/packages-exp/messaging-exp/src/listeners/window-listener.ts b/packages/messaging/src/listeners/window-listener.ts similarity index 100% rename from packages-exp/messaging-exp/src/listeners/window-listener.ts rename to packages/messaging/src/listeners/window-listener.ts diff --git a/packages-exp/messaging-exp/src/messaging-service.ts b/packages/messaging/src/messaging-service.ts similarity index 96% rename from packages-exp/messaging-exp/src/messaging-service.ts rename to packages/messaging/src/messaging-service.ts index ccb35762f2d..27333f106db 100644 --- a/packages-exp/messaging-exp/src/messaging-service.ts +++ b/packages/messaging/src/messaging-service.ts @@ -15,14 +15,14 @@ * limitations under the License. */ -import { FirebaseApp, _FirebaseService } from '@firebase/app-exp'; +import { FirebaseApp, _FirebaseService } from '@firebase/app'; import { MessagePayload, NextFn, Observer } from './interfaces/public-types'; import { FirebaseAnalyticsInternalName } from '@firebase/analytics-interop-types'; import { FirebaseInternalDependencies } from './interfaces/internal-dependencies'; import { LogEvent } from './interfaces/logging-types'; import { Provider } from '@firebase/component'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; import { extractAppConfig } from './helpers/extract-app-config'; export class MessagingService implements _FirebaseService { diff --git a/packages/messaging/src/testing/compare-headers.test.ts b/packages/messaging/src/testing/compare-headers.test.ts index 21fe9551874..ad171740ace 100644 --- a/packages/messaging/src/testing/compare-headers.test.ts +++ b/packages/messaging/src/testing/compare-headers.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import '../testing/setup'; +import './setup'; import { AssertionError, expect } from 'chai'; diff --git a/packages/messaging/src/testing/compare-headers.ts b/packages/messaging/src/testing/compare-headers.ts index 98bb73259df..6f760caf32d 100644 --- a/packages/messaging/src/testing/compare-headers.ts +++ b/packages/messaging/src/testing/compare-headers.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import '../testing/setup'; +import './setup'; import { expect } from 'chai'; diff --git a/packages/messaging/src/testing/fakes/firebase-dependencies.ts b/packages/messaging/src/testing/fakes/firebase-dependencies.ts index 58b0a6812c1..e1f87a2505e 100644 --- a/packages/messaging/src/testing/fakes/firebase-dependencies.ts +++ b/packages/messaging/src/testing/fakes/firebase-dependencies.ts @@ -19,11 +19,11 @@ import { FirebaseAnalyticsInternal, FirebaseAnalyticsInternalName } from '@firebase/analytics-interop-types'; -import { FirebaseApp, FirebaseOptions } from '@firebase/app-types'; -import { FirebaseInstallations } from '@firebase/installations-types'; import { FirebaseInternalDependencies } from '../../interfaces/internal-dependencies'; +import { FirebaseOptions } from '@firebase/app'; import { Provider } from '@firebase/component'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; import { extractAppConfig } from '../../helpers/extract-app-config'; export function getFakeFirebaseDependencies( @@ -38,7 +38,7 @@ export function getFakeFirebaseDependencies( }; } -export function getFakeApp(options: FirebaseOptions = {}): FirebaseApp { +export function getFakeApp(options: FirebaseOptions = {}): any { options = { apiKey: 'apiKey', projectId: 'projectId', @@ -54,21 +54,19 @@ export function getFakeApp(options: FirebaseOptions = {}): FirebaseApp { options, automaticDataCollectionEnabled: true, delete: async () => {}, - messaging: (() => null as unknown) as FirebaseApp['messaging'], + messaging: (() => null as unknown) as any, installations: () => getFakeInstallations() }; } -function getFakeInstallations(): FirebaseInstallations { +export function getFakeInstallations(): _FirebaseInstallationsInternal { return { getId: async () => 'FID', - getToken: async () => 'authToken', - delete: async () => undefined, - onIdChange: () => () => {} + getToken: async () => 'authToken' }; } -function getFakeAnalyticsProvider(): Provider { +export function getFakeAnalyticsProvider(): Provider { const analytics: FirebaseAnalyticsInternal = { logEvent() {} }; diff --git a/packages-exp/messaging-exp/src/testing/fakes/logging-object.ts b/packages/messaging/src/testing/fakes/logging-object.ts similarity index 100% rename from packages-exp/messaging-exp/src/testing/fakes/logging-object.ts rename to packages/messaging/src/testing/fakes/logging-object.ts diff --git a/packages-exp/messaging-exp/src/testing/fakes/messaging-service.ts b/packages/messaging/src/testing/fakes/messaging-service.ts similarity index 100% rename from packages-exp/messaging-exp/src/testing/fakes/messaging-service.ts rename to packages/messaging/src/testing/fakes/messaging-service.ts diff --git a/packages/messaging/src/testing/fakes/service-worker.ts b/packages/messaging/src/testing/fakes/service-worker.ts index 0e3f27c4da5..8cc09811e0e 100644 --- a/packages/messaging/src/testing/fakes/service-worker.ts +++ b/packages/messaging/src/testing/fakes/service-worker.ts @@ -15,10 +15,18 @@ * limitations under the License. */ +import { + Client, + Clients, + ExtendableEvent, + ServiceWorkerGlobalScope, + WindowClient +} from '../../util/sw-types'; + import { Writable } from 'ts-essentials'; // Add fake SW types. -declare const self: Window & Writable; +declare const self: Window & Writable; // When trying to stub self.clients self.registration, Sinon complains that these properties do not // exist. This is because we're not actually running these tests in a service worker context. @@ -39,8 +47,8 @@ export function mockServiceWorker(): void { } export function restoreServiceWorker(): void { - delete self.clients; - delete self.registration; + self.clients = new FakeClients(); + self.registration = new FakeServiceWorkerRegistration(); } class FakeClients implements Clients { @@ -93,8 +101,7 @@ class FakeWindowClient implements WindowClient { } export class FakeServiceWorkerRegistration - implements ServiceWorkerRegistration -{ + implements ServiceWorkerRegistration { active = null; installing = null; waiting = null; @@ -103,9 +110,9 @@ export class FakeServiceWorkerRegistration scope = '/scope-value'; // Unused in FCM Web SDK, no need to mock these. - navigationPreload = null as unknown as NavigationPreloadManager; - sync = null as unknown as SyncManager; - updateViaCache = null as unknown as ServiceWorkerUpdateViaCache; + navigationPreload = (null as unknown) as NavigationPreloadManager; + sync = (null as unknown) as SyncManager; + updateViaCache = (null as unknown) as ServiceWorkerUpdateViaCache; async getNotifications() { return []; @@ -161,8 +168,8 @@ export class FakePushSubscription implements PushSubscription { } // Unused in FCM - toJSON = null as unknown as () => PushSubscriptionJSON; - options = null as unknown as PushSubscriptionOptions; + toJSON = (null as unknown) as () => PushSubscriptionJSON; + options = (null as unknown) as PushSubscriptionOptions; } /** diff --git a/packages/messaging/src/testing/setup.ts b/packages/messaging/src/testing/setup.ts index 0d55cf43ef2..61b3524ca74 100644 --- a/packages/messaging/src/testing/setup.ts +++ b/packages/messaging/src/testing/setup.ts @@ -18,7 +18,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinonChai from 'sinon-chai'; -import { dbDelete } from '../helpers/idb-manager'; +import { dbDelete } from '../internals/idb-manager'; import { deleteDb } from 'idb'; import { restore } from 'sinon'; import { use } from 'chai'; diff --git a/packages/messaging/src/util/constants.ts b/packages/messaging/src/util/constants.ts index 3a10bab3361..8491380a5a0 100644 --- a/packages/messaging/src/util/constants.ts +++ b/packages/messaging/src/util/constants.ts @@ -23,21 +23,29 @@ export const DEFAULT_VAPID_KEY = export const ENDPOINT = 'https://fcmregistrations.googleapis.com/v1'; -// Key of FCM Payload in Notification's data field. +/** Key of FCM Payload in Notification's data field. */ export const FCM_MSG = 'FCM_MSG'; -export const TAG = 'FirebaseMessaging: '; -// Set to '1' if Analytics is enabled for the campaign -export const CONSOLE_CAMPAIGN_ANALYTICS_ENABLED = 'google.c.a.e'; export const CONSOLE_CAMPAIGN_ID = 'google.c.a.c_id'; -export const CONSOLE_CAMPAIGN_TIME = 'google.c.a.ts'; export const CONSOLE_CAMPAIGN_NAME = 'google.c.a.c_l'; +export const CONSOLE_CAMPAIGN_TIME = 'google.c.a.ts'; +/** Set to '1' if Analytics is enabled for the campaign */ +export const CONSOLE_CAMPAIGN_ANALYTICS_ENABLED = 'google.c.a.e'; +export const TAG = 'FirebaseMessaging: '; +export const MAX_NUMBER_OF_EVENTS_PER_LOG_REQUEST = 1000; +export const MAX_RETRIES = 3; +export const LOG_INTERVAL_IN_MS = 86400000; //24 hour +export const DEFAULT_BACKOFF_TIME_MS = 5000; + +// FCM log source name registered at Firelog: 'FCM_CLIENT_EVENT_LOGGING'. It uniquely identifies +// FCM's logging configuration. +export const FCM_LOG_SOURCE = 1249; -// Due to the fact that onBackgroundMessage can't be awaited (to support rxjs), a silent push -// warning might be shown by the browser if the callback fails to completes by the end of onPush. -// Experiments were ran to determine the majority onBackground message clock time. This brief -// blocking time would allow majority of the onBackgroundMessage callback to finish. -export const BACKGROUND_HANDLE_EXECUTION_TIME_LIMIT_MS = 1000; +// Defined as in proto/messaging_event.proto. Neglecting fields that are supported. +export const SDK_PLATFORM_WEB = 3; +export const EVENT_MESSAGE_DELIVERED = 1; -// Preparation time for client to initialize and set up the message handler. -export const FOREGROUND_HANDLE_PREPARATION_TIME_MS = 3000; +export enum MessageType { + DATA_MESSAGE = 1, + DISPLAY_NOTIFICATION = 3 +} diff --git a/packages/messaging/src/util/errors.ts b/packages/messaging/src/util/errors.ts index 6a8fca2fdef..14b001b6dc2 100644 --- a/packages/messaging/src/util/errors.ts +++ b/packages/messaging/src/util/errors.ts @@ -24,6 +24,7 @@ export const enum ErrorCode { PERMISSION_DEFAULT = 'permission-default', PERMISSION_BLOCKED = 'permission-blocked', UNSUPPORTED_BROWSER = 'unsupported-browser', + INDEXED_DB_UNSUPPORTED = 'indexed-db-unsupported', FAILED_DEFAULT_REGISTRATION = 'failed-service-worker-registration', TOKEN_SUBSCRIBE_FAILED = 'token-subscribe-failed', TOKEN_SUBSCRIBE_NO_TOKEN = 'token-subscribe-no-token', @@ -50,6 +51,8 @@ export const ERROR_MAP: ErrorMap = { 'The notification permission was not granted and blocked instead.', [ErrorCode.UNSUPPORTED_BROWSER]: "This browser doesn't support the API's required to use the firebase SDK.", + [ErrorCode.INDEXED_DB_UNSUPPORTED]: + "This browser doesn't support indexedDb.open() (ex. Safari iFrame, Firefox Private Browsing, etc)", [ErrorCode.FAILED_DEFAULT_REGISTRATION]: 'We are unable to register the default service worker. {$browserErrorMessage}', [ErrorCode.TOKEN_SUBSCRIBE_FAILED]: diff --git a/packages/messaging/src/util/sw-types.ts b/packages/messaging/src/util/sw-types.ts index 341ba64e5d1..587cc248f50 100644 --- a/packages/messaging/src/util/sw-types.ts +++ b/packages/messaging/src/util/sw-types.ts @@ -27,8 +27,7 @@ // Not the whole interface, just the parts we're currently using. If TS claims that something does // not exist on this, feel free to add it. -// eslint-disable-next-line @typescript-eslint/no-unused-vars -interface ServiceWorkerGlobalScope { +export interface ServiceWorkerGlobalScope { readonly location: WorkerLocation; readonly clients: Clients; readonly registration: ServiceWorkerRegistration; @@ -42,62 +41,45 @@ interface ServiceWorkerGlobalScope { ): void; } -// Only makes `clients` and `registration` optional because in tests, we reset them by calling -// `delete` and TS2790 enforces The operand of a `delete'` operator must be optional. -// eslint-disable-next-line @typescript-eslint/no-unused-vars -interface ServiceWorkerGlobalScopeForTesting { - readonly location: WorkerLocation; - readonly clients?: Clients; - readonly registration?: ServiceWorkerRegistration; - addEventListener( - type: K, - listener: ( - this: ServiceWorkerGlobalScope, - ev: ServiceWorkerGlobalScopeEventMap[K] - ) => any, - options?: boolean | AddEventListenerOptions - ): void; -} - // Same as the previous interface -interface ServiceWorkerGlobalScopeEventMap { +export interface ServiceWorkerGlobalScopeEventMap { notificationclick: NotificationEvent; push: PushEvent; pushsubscriptionchange: PushSubscriptionChangeEvent; } -interface Client { +export interface Client { readonly id: string; readonly type: ClientTypes; readonly url: string; postMessage(message: any, transfer?: Transferable[]): void; } -interface ClientQueryOptions { +export interface ClientQueryOptions { includeReserved?: boolean; includeUncontrolled?: boolean; type?: ClientTypes; } -interface WindowClient extends Client { +export interface WindowClient extends Client { readonly focused: boolean; readonly visibilityState: VisibilityState; focus(): Promise; navigate(url: string): Promise; } -interface Clients { +export interface Clients { claim(): Promise; get(id: string): Promise; matchAll(options?: ClientQueryOptions): Promise; openWindow(url: string): Promise; } -interface ExtendableEvent extends Event { +export interface ExtendableEvent extends Event { waitUntil(f: Promise): void; } -interface NotificationEvent extends ExtendableEvent { +export interface NotificationEvent extends ExtendableEvent { readonly action: string; readonly notification: Notification; } @@ -109,11 +91,11 @@ interface PushMessageData { text(): string; } -interface PushEvent extends ExtendableEvent { +export interface PushEvent extends ExtendableEvent { readonly data: PushMessageData | null; } -interface PushSubscriptionChangeEvent extends ExtendableEvent { +export interface PushSubscriptionChangeEvent extends ExtendableEvent { readonly newSubscription: PushSubscription | null; readonly oldSubscription: PushSubscription | null; } diff --git a/packages-exp/messaging-exp/sw/package.json b/packages/messaging/sw/package.json similarity index 83% rename from packages-exp/messaging-exp/sw/package.json rename to packages/messaging/sw/package.json index 10abf5a7d17..2eceb18b972 100644 --- a/packages-exp/messaging-exp/sw/package.json +++ b/packages/messaging/sw/package.json @@ -1,5 +1,5 @@ { - "name": "@firebase/messaging-exp", + "name": "@firebase/messaging", "description": "", "author": "Firebase (https://firebase.google.com/)", "module": "../dist/index.sw.esm2017.js", diff --git a/packages-exp/installations-compat/.eslintrc.js b/packages/performance-compat/.eslintrc.js similarity index 100% rename from packages-exp/installations-compat/.eslintrc.js rename to packages/performance-compat/.eslintrc.js diff --git a/packages-exp/performance-compat/README.md b/packages/performance-compat/README.md similarity index 100% rename from packages-exp/performance-compat/README.md rename to packages/performance-compat/README.md diff --git a/packages-exp/performance-compat/karma.conf.js b/packages/performance-compat/karma.conf.js similarity index 100% rename from packages-exp/performance-compat/karma.conf.js rename to packages/performance-compat/karma.conf.js diff --git a/packages-exp/performance-compat/package.json b/packages/performance-compat/package.json similarity index 86% rename from packages-exp/performance-compat/package.json rename to packages/performance-compat/package.json index d32b48dcdc8..569e7bf910e 100644 --- a/packages-exp/performance-compat/package.json +++ b/packages/performance-compat/package.json @@ -3,7 +3,6 @@ "version": "0.0.900", "description": "The compatibility package of Firebase Performance", "author": "Firebase (https://firebase.google.com/)", - "private": true, "main": "dist/index.cjs.js", "browser": "dist/index.esm2017.js", "module": "dist/index.esm2017.js", @@ -23,14 +22,14 @@ "test:browser": "karma start --single-run", "test:browser:debug": "karma start --browsers Chrome --auto-watch", "prettier": "prettier --write '{src,test}/**/*.{js,ts}'", - "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../performance-exp/dist/src/index.d.ts -o dist/src/index.d.ts -a -r FirebasePerformance:FirebasePerformanceCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/performance" + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../performance/dist/src/index.d.ts -o dist/src/index.d.ts -a -r FirebasePerformance:FirebasePerformanceCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/performance" }, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-compat": "0.x" }, "dependencies": { - "@firebase/performance-exp": "0.0.900", + "@firebase/performance": "0.4.18", "@firebase/performance-types": "0.0.13", "@firebase/util": "1.3.0", "@firebase/logger": "0.2.6", @@ -46,7 +45,7 @@ "@firebase/app-compat": "0.0.900" }, "repository": { - "directory": "packages-exp/performance-compat", + "directory": "packages/performance-compat", "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, diff --git a/packages-exp/app-exp/rollup.config.js b/packages/performance-compat/rollup.config.js similarity index 61% rename from packages-exp/app-exp/rollup.config.js rename to packages/performance-compat/rollup.config.js index 5dc6ca47633..13612393d50 100644 --- a/packages-exp/app-exp/rollup.config.js +++ b/packages/performance-compat/rollup.config.js @@ -19,12 +19,8 @@ import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; import json from '@rollup/plugin-json'; import pkg from './package.json'; -import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; - -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); +const deps = Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)); /** * ES5 Builds */ @@ -35,10 +31,20 @@ const es5BuildPlugins = [ json() ]; -const es5Builds = es5BuildsNoPlugin.map(build => ({ - ...build, - plugins: es5BuildPlugins -})); +const es5Builds = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins + } +]; /** * ES2017 Builds @@ -57,9 +63,20 @@ const es2017BuildPlugins = [ }) ]; -const es2017Builds = es2017BuildsNoPlugin.map(build => ({ - ...build, - plugins: es2017BuildPlugins -})); +const es2017Builds = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins + } +]; export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/performance-compat/src/index.ts b/packages/performance-compat/src/index.ts similarity index 86% rename from packages-exp/performance-compat/src/index.ts rename to packages/performance-compat/src/index.ts index 0a2109ee3f0..908a59a6211 100644 --- a/packages-exp/performance-compat/src/index.ts +++ b/packages/performance-compat/src/index.ts @@ -25,13 +25,6 @@ import { PerformanceCompatImpl } from './performance'; import { name as packageName, version } from '../package.json'; import { FirebasePerformance as FirebasePerformanceCompat } from '@firebase/performance-types'; -// TODO: move it to the future performance-compat-types package -declare module '@firebase/component' { - interface NameServiceMapping { - 'performance-compat': FirebasePerformanceCompat; - } -} - function registerPerformanceCompat(firebaseInstance: _FirebaseNamespace): void { firebaseInstance.INTERNAL.registerComponent( new Component( @@ -49,7 +42,7 @@ function performanceFactory( ): PerformanceCompatImpl { const app = container.getProvider('app-compat').getImmediate(); // The following call will always succeed. - const performance = container.getProvider('performance-exp').getImmediate(); + const performance = container.getProvider('performance').getImmediate(); return new PerformanceCompatImpl(app, performance); } diff --git a/packages-exp/performance-compat/src/performance.test.ts b/packages/performance-compat/src/performance.test.ts similarity index 97% rename from packages-exp/performance-compat/src/performance.test.ts rename to packages/performance-compat/src/performance.test.ts index 1ef81428e7b..7dc2ff82fcd 100644 --- a/packages-exp/performance-compat/src/performance.test.ts +++ b/packages/performance-compat/src/performance.test.ts @@ -23,7 +23,7 @@ import { getFakeModularPerformance, getFakeModularPerformanceTrace } from '../test/util'; -import * as perfModularApi from '@firebase/performance-exp'; +import * as perfModularApi from '@firebase/performance'; import { PerformanceCompatImpl } from './performance'; describe('Performance Compat', () => { diff --git a/packages-exp/performance-compat/src/performance.ts b/packages/performance-compat/src/performance.ts similarity index 97% rename from packages-exp/performance-compat/src/performance.ts rename to packages/performance-compat/src/performance.ts index ddc82c88d5a..b15f83eb67a 100644 --- a/packages-exp/performance-compat/src/performance.ts +++ b/packages/performance-compat/src/performance.ts @@ -20,7 +20,7 @@ import { FirebasePerformance, // The PerformanceTrace type has not changed between modular and non-modular packages. PerformanceTrace -} from '@firebase/performance-exp'; +} from '@firebase/performance'; import { FirebasePerformance as FirebasePerformanceCompat } from '@firebase/performance-types'; import { FirebaseApp, _FirebaseService } from '@firebase/app-compat'; diff --git a/packages-exp/app-exp/test/setup.ts b/packages/performance-compat/test/setup.ts similarity index 100% rename from packages-exp/app-exp/test/setup.ts rename to packages/performance-compat/test/setup.ts diff --git a/packages-exp/performance-compat/test/util.ts b/packages/performance-compat/test/util.ts similarity index 97% rename from packages-exp/performance-compat/test/util.ts rename to packages/performance-compat/test/util.ts index 5bc5e3e56ff..0cd8e21c523 100644 --- a/packages-exp/performance-compat/test/util.ts +++ b/packages/performance-compat/test/util.ts @@ -19,7 +19,7 @@ import { FirebaseApp } from '@firebase/app-compat'; import { FirebasePerformance, PerformanceTrace -} from '@firebase/performance-exp'; +} from '@firebase/performance'; export function getFakeApp(): FirebaseApp { return { diff --git a/packages-exp/performance-compat/tsconfig.json b/packages/performance-compat/tsconfig.json similarity index 100% rename from packages-exp/performance-compat/tsconfig.json rename to packages/performance-compat/tsconfig.json diff --git a/packages/performance-types/index.d.ts b/packages/performance-types/index.d.ts index 55463ee8434..c295b312625 100644 --- a/packages/performance-types/index.d.ts +++ b/packages/performance-types/index.d.ts @@ -114,6 +114,6 @@ export interface PerformanceTrace { declare module '@firebase/component' { interface NameServiceMapping { - 'performance': FirebasePerformance; + 'performance-compat': FirebasePerformance; } } diff --git a/packages/performance/.eslintrc.js b/packages/performance/.eslintrc.js index 16276950320..ca80aa0f69a 100644 --- a/packages/performance/.eslintrc.js +++ b/packages/performance/.eslintrc.js @@ -1,3 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + module.exports = { extends: '../../config/.eslintrc.js', parserOptions: { diff --git a/packages-exp/performance-exp/api-extractor.json b/packages/performance/api-extractor.json similarity index 100% rename from packages-exp/performance-exp/api-extractor.json rename to packages/performance/api-extractor.json diff --git a/packages/performance/index.ts b/packages/performance/index.ts deleted file mode 100644 index 6829a9154a1..00000000000 --- a/packages/performance/index.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '@firebase/app'; -import '@firebase/installations'; -import { FirebaseApp, FirebaseNamespace } from '@firebase/app-types'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { PerformanceController } from './src/controllers/perf'; -import { setupApi } from './src/services/api_service'; -import { SettingsService } from './src/services/settings_service'; -import { ERROR_FACTORY, ErrorCode } from './src/utils/errors'; -import { FirebasePerformance } from '@firebase/performance-types'; -import { Component, ComponentType } from '@firebase/component'; -import { FirebaseInstallations } from '@firebase/installations-types'; -import { name, version } from './package.json'; - -const DEFAULT_ENTRY_NAME = '[DEFAULT]'; - -export function registerPerformance(instance: FirebaseNamespace): void { - const factoryMethod = ( - app: FirebaseApp, - installations: FirebaseInstallations - ): PerformanceController => { - if (app.name !== DEFAULT_ENTRY_NAME) { - throw ERROR_FACTORY.create(ErrorCode.FB_NOT_DEFAULT); - } - if (typeof window === 'undefined') { - throw ERROR_FACTORY.create(ErrorCode.NO_WINDOW); - } - setupApi(window); - SettingsService.getInstance().firebaseAppInstance = app; - SettingsService.getInstance().installationsService = installations; - return new PerformanceController(app); - }; - - // Register performance with firebase-app. - (instance as _FirebaseNamespace).INTERNAL.registerComponent( - new Component( - 'performance', - container => { - /* Dependencies */ - // getImmediate for FirebaseApp will always succeed - const app = container.getProvider('app').getImmediate(); - // The following call will always succeed because perf has `import '@firebase/installations'` - const installations = container - .getProvider('installations') - .getImmediate(); - - return factoryMethod(app, installations); - }, - ComponentType.PUBLIC - ) - ); - - instance.registerVersion(name, version); -} - -registerPerformance(firebase); - -declare module '@firebase/app-types' { - interface FirebaseNamespace { - performance?: { - (app?: FirebaseApp): FirebasePerformance; - }; - } - interface FirebaseApp { - performance?(): FirebasePerformance; - } -} diff --git a/packages/performance/karma.conf.js b/packages/performance/karma.conf.js index 7d6d24d6c2b..7f333fdb2fd 100644 --- a/packages/performance/karma.conf.js +++ b/packages/performance/karma.conf.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/package.json b/packages/performance/package.json index 199be2efad7..10897e8c704 100644 --- a/packages/performance/package.json +++ b/packages/performance/package.json @@ -4,33 +4,34 @@ "description": "Firebase performance for web", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", "files": [ "dist" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c", + "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/performance --include-dependencies build", + "build:release": "yarn build", "dev": "rollup -c -w", "test": "run-p lint test:browser", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", "test:browser": "karma start --single-run", "test:debug": "karma start --browsers=Chrome --auto-watch", - "prettier": "prettier --write '{src,test}/**/*.{js,ts}'" + "prettier": "prettier --write '{src,test}/**/*.{js,ts}'", + "api-report": "api-extractor run --local --verbose", + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" }, "dependencies": { "@firebase/logger": "0.2.6", "@firebase/installations": "0.4.32", "@firebase/util": "1.3.0", - "@firebase/performance-types": "0.0.13", "@firebase/component": "0.5.6", "tslib": "^2.1.0" }, @@ -50,11 +51,12 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/index.d.ts", + "typings": "dist/src/index.d.ts", "nyc": { "extension": [ ".ts" ], "reportDir": "./coverage/node" - } -} + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages/performance/rollup.config.js b/packages/performance/rollup.config.js index bff20f3d6ca..631f450f1f7 100644 --- a/packages/performance/rollup.config.js +++ b/packages/performance/rollup.config.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,7 @@ import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; import pkg from './package.json'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); - +const deps = Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)); /** * ES5 Builds */ @@ -31,20 +28,19 @@ const es5BuildPlugins = [typescriptPlugin({ typescript }), json()]; const es5Builds = [ { - input: 'index.ts', + input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } + { file: pkg.esm5, format: 'es', sourcemap: true } ], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins } ]; /** * ES2017 Builds */ - const es2017BuildPlugins = [ typescriptPlugin({ typescript, @@ -59,10 +55,10 @@ const es2017BuildPlugins = [ const es2017Builds = [ { - input: 'index.ts', - output: [{ file: pkg.esm2017, format: 'es', sourcemap: true }], - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + input: 'src/index.ts', + output: [{ file: pkg.browser, format: 'es', sourcemap: true }], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins } ]; diff --git a/packages/performance/src/constants.ts b/packages/performance/src/constants.ts index b0566a2a8be..2cac126da97 100644 --- a/packages/performance/src/constants.ts +++ b/packages/performance/src/constants.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/src/controllers/perf.test.ts b/packages/performance/src/controllers/perf.test.ts index 6d3a24e723f..cf46bb48276 100644 --- a/packages/performance/src/controllers/perf.test.ts +++ b/packages/performance/src/controllers/perf.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,12 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import { PerformanceController } from '../controllers/perf'; -import { Trace } from '../resources/trace'; import { Api, setupApi } from '../services/api_service'; -import { FirebaseApp } from '@firebase/app-types'; import * as initializationService from '../services/initialization_service'; -import * as FirebaseUtil from '@firebase/util'; +import { SettingsService } from '../services/settings_service'; import { consoleLogger } from '../utils/console_logger'; +import { FirebaseApp } from '@firebase/app'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; import '../../test/setup'; describe('Firebase Performance Test', () => { @@ -43,95 +43,109 @@ describe('Firebase Performance Test', () => { options: fakeFirebaseConfig } as unknown) as FirebaseApp; + const fakeInstallations = ({} as unknown) as _FirebaseInstallationsInternal; + describe('#constructor', () => { it('does not initialize performance if the required apis are not available', () => { stub(Api.prototype, 'requiredApisAvailable').returns(false); - stub(initializationService, 'getInitializationPromise'); - new PerformanceController(fakeFirebaseApp); - expect(initializationService.getInitializationPromise).not.be.called; - }); - it('does not initialize performance if validateIndexedDBOpenable return false', async () => { - stub(Api.prototype, 'requiredApisAvailable').returns(true); - const validateStub = stub( - FirebaseUtil, - 'validateIndexedDBOpenable' - ).resolves(false); - stub(initializationService, 'getInitializationPromise'); - new PerformanceController(fakeFirebaseApp); - await validateStub; - expect(initializationService.getInitializationPromise).not.be.called; - }); - - it('does not initialize performance if validateIndexedDBOpenable throws an error', async () => { - stub(Api.prototype, 'requiredApisAvailable').returns(true); - const validateStub = stub( - FirebaseUtil, - 'validateIndexedDBOpenable' - ).rejects(); - stub(initializationService, 'getInitializationPromise'); stub(consoleLogger, 'info'); - new PerformanceController(fakeFirebaseApp); - try { - await validateStub; - expect(initializationService.getInitializationPromise).not.be.called; - expect(consoleLogger.info).be.called; - } catch (ignored) {} - }); - }); - - describe('#trace', () => { - it('creates a custom trace', () => { - const controller = new PerformanceController(fakeFirebaseApp); - const myTrace = controller.trace('myTrace'); + const performanceController = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + performanceController._init(); - expect(myTrace).to.be.instanceOf(Trace); - }); - - it('custom trace has the correct name', () => { - const controller = new PerformanceController(fakeFirebaseApp); - const myTrace = controller.trace('myTrace'); - - expect(myTrace.name).is.equal('myTrace'); - }); - - it('custom trace is not auto', () => { - const controller = new PerformanceController(fakeFirebaseApp); - const myTrace = controller.trace('myTrace'); - - expect(myTrace.isAuto).is.equal(false); + expect(initializationService.getInitializationPromise).not.be.called; + expect(consoleLogger.info).to.be.calledWithMatch( + /.*Fetch.*Promise.*cookies.*/ + ); }); }); - describe('#instrumentationEnabled', () => { - it('sets instrumentationEnabled to enabled', async () => { - const controller = new PerformanceController(fakeFirebaseApp); - - controller.instrumentationEnabled = true; - expect(controller.instrumentationEnabled).is.equal(true); + describe('#settings', () => { + it('applies the settings if provided', async () => { + const settings = { + instrumentationEnabled: false, + dataCollectionEnabled: false + }; + + const performance = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + performance._init(settings); + + expect(performance.instrumentationEnabled).is.equal(false); + expect(performance.dataCollectionEnabled).is.equal(false); }); - it('sets instrumentationEnabled to disabled', async () => { - const controller = new PerformanceController(fakeFirebaseApp); - - controller.instrumentationEnabled = false; - expect(controller.instrumentationEnabled).is.equal(false); + it('uses defaults when settings are not provided', async () => { + const expectedInstrumentationEnabled = SettingsService.getInstance() + .instrumentationEnabled; + const expectedDataCollectionEnabled = SettingsService.getInstance() + .dataCollectionEnabled; + + const performance = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + performance._init(); + + expect(performance.instrumentationEnabled).is.equal( + expectedInstrumentationEnabled + ); + expect(performance.dataCollectionEnabled).is.equal( + expectedDataCollectionEnabled + ); }); - }); - - describe('#dataCollectionEnabled', () => { - it('sets dataCollectionEnabled to enabled', async () => { - const controller = new PerformanceController(fakeFirebaseApp); - controller.dataCollectionEnabled = true; - expect(controller.dataCollectionEnabled).is.equal(true); + describe('#instrumentationEnabled', () => { + it('sets instrumentationEnabled to enabled', async () => { + const performance = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + performance._init(); + + performance.instrumentationEnabled = true; + expect(performance.instrumentationEnabled).is.equal(true); + }); + + it('sets instrumentationEnabled to disabled', async () => { + const performance = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + performance._init(); + + performance.instrumentationEnabled = false; + expect(performance.instrumentationEnabled).is.equal(false); + }); }); - it('sets dataCollectionEnabled to disabled', () => { - const controller = new PerformanceController(fakeFirebaseApp); - - controller.dataCollectionEnabled = false; - expect(controller.dataCollectionEnabled).is.equal(false); + describe('#dataCollectionEnabled', () => { + it('sets dataCollectionEnabled to enabled', async () => { + const performance = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + performance._init(); + + performance.dataCollectionEnabled = true; + expect(performance.dataCollectionEnabled).is.equal(true); + }); + + it('sets dataCollectionEnabled to disabled', () => { + const performance = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + performance._init(); + + performance.dataCollectionEnabled = false; + expect(performance.dataCollectionEnabled).is.equal(false); + }); }); }); }); diff --git a/packages/performance/src/controllers/perf.ts b/packages/performance/src/controllers/perf.ts index 9793f614dd4..db2d1820405 100644 --- a/packages/performance/src/controllers/perf.ts +++ b/packages/performance/src/controllers/perf.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,39 +14,70 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Trace } from '../resources/trace'; + import { setupOobResources } from '../services/oob_resources_service'; import { SettingsService } from '../services/settings_service'; import { getInitializationPromise } from '../services/initialization_service'; import { Api } from '../services/api_service'; -import { FirebaseApp } from '@firebase/app-types'; -import { FirebasePerformance } from '@firebase/performance-types'; -import { setupTransportService } from '../services/transport_service'; +import { FirebaseApp } from '@firebase/app'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; +import { PerformanceSettings, FirebasePerformance } from '../public_types'; import { validateIndexedDBOpenable } from '@firebase/util'; +import { setupTransportService } from '../services/transport_service'; import { consoleLogger } from '../utils/console_logger'; + export class PerformanceController implements FirebasePerformance { - constructor(readonly app: FirebaseApp) { + private initialized: boolean = false; + + constructor( + readonly app: FirebaseApp, + readonly installations: _FirebaseInstallationsInternal + ) {} + + /** + * This method *must* be called internally as part of creating a + * PerformanceController instance. + * + * Currently it's not possible to pass the settings object through the + * constructor using Components, so this method exists to be called with the + * desired settings, to ensure nothing is collected without the user's + * consent. + */ + _init(settings?: PerformanceSettings): void { + if (this.initialized) { + return; + } + + if (settings?.dataCollectionEnabled !== undefined) { + this.dataCollectionEnabled = settings.dataCollectionEnabled; + } + if (settings?.instrumentationEnabled !== undefined) { + this.instrumentationEnabled = settings.instrumentationEnabled; + } + if (Api.getInstance().requiredApisAvailable()) { validateIndexedDBOpenable() .then(isAvailable => { if (isAvailable) { setupTransportService(); - getInitializationPromise().then( - setupOobResources, - setupOobResources + getInitializationPromise(this).then( + () => setupOobResources(this), + () => setupOobResources(this) ); + this.initialized = true; } }) .catch(error => { consoleLogger.info(`Environment doesn't support IndexedDB: ${error}`); }); + } else { + consoleLogger.info( + 'Firebase Performance cannot start if the browser does not support ' + + '"Fetch" and "Promise", or cookies are disabled.' + ); } } - trace(name: string): Trace { - return new Trace(name); - } - set instrumentationEnabled(val: boolean) { SettingsService.getInstance().instrumentationEnabled = val; } diff --git a/packages-exp/performance-exp/src/index.test.ts b/packages/performance/src/index.test.ts similarity index 99% rename from packages-exp/performance-exp/src/index.test.ts rename to packages/performance/src/index.test.ts index 9d8403f954d..d60f5897114 100644 --- a/packages-exp/performance-exp/src/index.test.ts +++ b/packages/performance/src/index.test.ts @@ -19,7 +19,7 @@ import { expect } from 'chai'; import { initializePerformance } from './index'; import { ERROR_FACTORY, ErrorCode } from './utils/errors'; import '../test/setup'; -import { deleteApp, FirebaseApp, initializeApp } from '@firebase/app-exp'; +import { deleteApp, FirebaseApp, initializeApp } from '@firebase/app'; const fakeFirebaseConfig = { apiKey: 'api-key', diff --git a/packages-exp/performance-exp/src/index.ts b/packages/performance/src/index.ts similarity index 90% rename from packages-exp/performance-exp/src/index.ts rename to packages/performance/src/index.ts index d9d3ead78d4..7691596b982 100644 --- a/packages-exp/performance-exp/src/index.ts +++ b/packages/performance/src/index.ts @@ -35,7 +35,7 @@ import { registerVersion, FirebaseApp, getApp -} from '@firebase/app-exp'; +} from '@firebase/app'; import { InstanceFactory, ComponentContainer, @@ -44,7 +44,7 @@ import { } from '@firebase/component'; import { name, version } from '../package.json'; import { Trace } from './resources/trace'; -import '@firebase/installations-exp'; +import '@firebase/installations'; import { deepEqual, getModularInstance } from '@firebase/util'; const DEFAULT_ENTRY_NAME = '[DEFAULT]'; @@ -58,7 +58,7 @@ export function getPerformance( app: FirebaseApp = getApp() ): FirebasePerformance { app = getModularInstance(app); - const provider = _getProvider(app, 'performance-exp'); + const provider = _getProvider(app, 'performance'); const perfInstance = provider.getImmediate() as PerformanceController; return perfInstance; } @@ -74,7 +74,7 @@ export function initializePerformance( settings?: PerformanceSettings ): FirebasePerformance { app = getModularInstance(app); - const provider = _getProvider(app, 'performance-exp'); + const provider = _getProvider(app, 'performance'); // throw if an instance was already created. // It could happen if initializePerformance() is called more than once, or getPerformance() is called first. @@ -108,14 +108,14 @@ export function trace( return new Trace(performance as PerformanceController, name); } -const factory: InstanceFactory<'performance-exp'> = ( +const factory: InstanceFactory<'performance'> = ( container: ComponentContainer, { options: settings }: { options?: PerformanceSettings } ) => { // Dependencies - const app = container.getProvider('app-exp').getImmediate(); + const app = container.getProvider('app').getImmediate(); const installations = container - .getProvider('installations-exp-internal') + .getProvider('installations-internal') .getImmediate(); if (app.name !== DEFAULT_ENTRY_NAME) { @@ -133,7 +133,7 @@ const factory: InstanceFactory<'performance-exp'> = ( function registerPerformance(): void { _registerComponent( - new Component('performance-exp', factory, ComponentType.PUBLIC) + new Component('performance', factory, ComponentType.PUBLIC) ); } diff --git a/packages-exp/performance-exp/src/public_types.ts b/packages/performance/src/public_types.ts similarity index 94% rename from packages-exp/performance-exp/src/public_types.ts rename to packages/performance/src/public_types.ts index 756fca2febf..56b3c177697 100644 --- a/packages-exp/performance-exp/src/public_types.ts +++ b/packages/performance/src/public_types.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { FirebaseApp } from '@firebase/app'; + /** * Defines configuration options for the Performance Monitoring SDK. * @@ -34,6 +36,11 @@ export interface PerformanceSettings { * @public */ export interface FirebasePerformance { + /** + * The {@link @firebase/app#FirebaseApp} this `FirebasePerformance` instance is associated with. + */ + app: FirebaseApp; + /** * Controls the logging of automatic traces and HTTP/S network monitoring. */ @@ -130,6 +137,6 @@ export interface PerformanceTrace { declare module '@firebase/component' { interface NameServiceMapping { - 'performance-exp': FirebasePerformance; + 'performance': FirebasePerformance; } } diff --git a/packages/performance/src/resources/network_request.test.ts b/packages/performance/src/resources/network_request.test.ts index 2484b08fdf2..e7e8d82a198 100644 --- a/packages/performance/src/resources/network_request.test.ts +++ b/packages/performance/src/resources/network_request.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,24 @@ import { expect } from 'chai'; import { Api, setupApi } from '../services/api_service'; import * as perfLogger from '../services/perf_logger'; +import { FirebaseApp } from '@firebase/app'; +import { PerformanceController } from '../controllers/perf'; +import { FirebaseInstallations } from '@firebase/installations-types'; import '../../test/setup'; describe('Firebase Performance > network_request', () => { setupApi(window); + const fakeFirebaseApp = ({ + options: {} + } as unknown) as FirebaseApp; + + const fakeInstallations = ({} as unknown) as FirebaseInstallations; + const performanceController = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + beforeEach(() => { stub(Api.prototype, 'getTimeOrigin').returns(1528521843799.5032); stub(perfLogger, 'logNetworkRequest'); @@ -46,6 +59,7 @@ describe('Firebase Performance > network_request', () => { } as unknown) as PerformanceResourceTiming; const EXPECTED_NETWORK_REQUEST = { + performanceController, url: 'http://some.test.website.com', responsePayloadBytes: 500, startTimeUs: 1528523489152135, @@ -53,7 +67,7 @@ describe('Firebase Performance > network_request', () => { timeToResponseCompletedUs: 8200 }; - createNetworkRequestEntry(PERFORMANCE_ENTRY); + createNetworkRequestEntry(performanceController, PERFORMANCE_ENTRY); expect( (perfLogger.logNetworkRequest as any).calledWith( @@ -70,7 +84,7 @@ describe('Firebase Performance > network_request', () => { responseEnd: 1645360.832443 } as unknown) as PerformanceResourceTiming; - createNetworkRequestEntry(PERFORMANCE_ENTRY); + createNetworkRequestEntry(performanceController, PERFORMANCE_ENTRY); expect(perfLogger.logNetworkRequest).to.not.have.been.called; }); diff --git a/packages/performance/src/resources/network_request.ts b/packages/performance/src/resources/network_request.ts index ac7cfe114fe..c5a8103eb03 100644 --- a/packages/performance/src/resources/network_request.ts +++ b/packages/performance/src/resources/network_request.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import { Api } from '../services/api_service'; import { logNetworkRequest } from '../services/perf_logger'; +import { PerformanceController } from '../controllers/perf'; // The order of values of this enum should not be changed. export const enum HttpMethod { @@ -34,6 +35,7 @@ export const enum HttpMethod { // Durations are in microseconds. export interface NetworkRequest { + performanceController: PerformanceController; url: string; httpMethod?: HttpMethod; requestPayloadBytes?: number; @@ -46,7 +48,10 @@ export interface NetworkRequest { timeToResponseCompletedUs?: number; } -export function createNetworkRequestEntry(entry: PerformanceEntry): void { +export function createNetworkRequestEntry( + performanceController: PerformanceController, + entry: PerformanceEntry +): void { const performanceEntry = entry as PerformanceResourceTiming; if (!performanceEntry || performanceEntry.responseStart === undefined) { return; @@ -66,6 +71,7 @@ export function createNetworkRequestEntry(entry: PerformanceEntry): void { // Remove the query params from logged network request url. const url = performanceEntry.name && performanceEntry.name.split('?')[0]; const networkRequest: NetworkRequest = { + performanceController, url, responsePayloadBytes: performanceEntry.transferSize, startTimeUs, diff --git a/packages/performance/src/resources/trace.test.ts b/packages/performance/src/resources/trace.test.ts index 19037380762..e4b42495a0d 100644 --- a/packages/performance/src/resources/trace.test.ts +++ b/packages/performance/src/resources/trace.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,14 +20,37 @@ import { Trace } from '../resources/trace'; import { expect } from 'chai'; import { Api, setupApi } from '../services/api_service'; import * as perfLogger from '../services/perf_logger'; +import { PerformanceController } from '../controllers/perf'; +import { FirebaseApp } from '@firebase/app'; +import { FirebaseInstallations } from '@firebase/installations-types'; import '../../test/setup'; describe('Firebase Performance > trace', () => { setupApi(window); + const fakeFirebaseConfig = { + apiKey: 'api-key', + authDomain: 'project-id.firebaseapp.com', + databaseURL: 'https://project-id.firebaseio.com', + projectId: 'project-id', + storageBucket: 'project-id.appspot.com', + messagingSenderId: 'sender-id', + appId: '1:111:web:a1234' + }; + + const fakeFirebaseApp = ({ + options: fakeFirebaseConfig + } as unknown) as FirebaseApp; + + const fakeInstallations = ({} as unknown) as FirebaseInstallations; + const performanceController = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + let trace: Trace; const createTrace = (): Trace => { - return new Trace('test'); + return new Trace(performanceController, 'test'); }; beforeEach(() => { @@ -81,6 +104,12 @@ describe('Firebase Performance > trace', () => { expect(() => trace.record(1000, -200)).to.throw(); }); + it('logs a trace without metrics or custom attributes', () => { + trace.record(1, 20); + + expect((perfLogger.logTrace as any).calledOnceWith(trace)).to.be.true; + }); + it('logs a trace with metrics', () => { trace.record(1, 20, { metrics: { cacheHits: 1 } }); diff --git a/packages/performance/src/resources/trace.ts b/packages/performance/src/resources/trace.ts index 6fe565de32b..67958572984 100644 --- a/packages/performance/src/resources/trace.ts +++ b/packages/performance/src/resources/trace.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,8 @@ import { isValidMetricName, convertMetricValueToInteger } from '../utils/metric_utils'; -import { PerformanceTrace } from '@firebase/performance-types'; +import { PerformanceTrace } from '../public_types'; +import { PerformanceController } from '../controllers/perf'; const enum TraceState { UNINITIALIZED = 1, @@ -56,6 +57,7 @@ export class Trace implements PerformanceTrace { private traceMeasure!: string; /** + * @param performanceController The performance controller running. * @param name The name of the trace. * @param isAuto If the trace is auto-instrumented. * @param traceMeasureName The name of the measure marker in user timing specification. This field @@ -63,6 +65,7 @@ export class Trace implements PerformanceTrace { * api (performance.mark and performance.measure). */ constructor( + readonly performanceController: PerformanceController, readonly name: string, readonly isAuto = false, traceMeasureName?: string @@ -271,6 +274,7 @@ export class Trace implements PerformanceTrace { * @param firstInputDelay First input delay in millisec */ static createOobTrace( + performanceController: PerformanceController, navigationTimings: PerformanceNavigationTiming[], paintTimings: PerformanceEntry[], firstInputDelay?: number @@ -279,7 +283,11 @@ export class Trace implements PerformanceTrace { if (!route) { return; } - const trace = new Trace(OOB_TRACE_PAGE_LOAD_PREFIX + route, true); + const trace = new Trace( + performanceController, + OOB_TRACE_PAGE_LOAD_PREFIX + route, + true + ); const timeOriginUs = Math.floor(Api.getInstance().getTimeOrigin() * 1000); trace.setStartTime(timeOriginUs); @@ -333,8 +341,16 @@ export class Trace implements PerformanceTrace { logTrace(trace); } - static createUserTimingTrace(measureName: string): void { - const trace = new Trace(measureName, false, measureName); + static createUserTimingTrace( + performanceController: PerformanceController, + measureName: string + ): void { + const trace = new Trace( + performanceController, + measureName, + false, + measureName + ); logTrace(trace); } } diff --git a/packages/performance/src/services/api_service.test.ts b/packages/performance/src/services/api_service.test.ts index 32ac012ffc3..c0e9a29b690 100644 --- a/packages/performance/src/services/api_service.test.ts +++ b/packages/performance/src/services/api_service.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import { stub } from 'sinon'; import { expect } from 'chai'; import { Api, setupApi } from './api_service'; import '../../test/setup'; + describe('Firebase Performance > api_service', () => { const PAGE_URL = 'http://www.test.com/abcd?a=2'; const PERFORMANCE_ENTRY: PerformanceEntry = { diff --git a/packages/performance/src/services/api_service.ts b/packages/performance/src/services/api_service.ts index df719e8fb16..f500e415e87 100644 --- a/packages/performance/src/services/api_service.ts +++ b/packages/performance/src/services/api_service.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,11 @@ import { ERROR_FACTORY, ErrorCode } from '../utils/errors'; import { isIndexedDBAvailable } from '@firebase/util'; import { consoleLogger } from '../utils/console_logger'; + declare global { interface Window { PerformanceObserver: typeof PerformanceObserver; - // eslint-disable-next-line @typescript-eslint/ban-types - perfMetrics?: { onFirstInputDelay: Function }; + perfMetrics?: { onFirstInputDelay(fn: (fid: number) => void): void }; } } @@ -46,8 +46,7 @@ export class Api { /** PreformanceObserver constructor function. */ private readonly PerformanceObserver: typeof PerformanceObserver; private readonly windowLocation: Location; - // eslint-disable-next-line @typescript-eslint/ban-types - readonly onFirstInputDelay?: Function; + readonly onFirstInputDelay?: (fn: (fid: number) => void) => void; readonly localStorage?: Storage; readonly document: Document; readonly navigator: Navigator; diff --git a/packages/performance/src/services/iid_service.test.ts b/packages/performance/src/services/iid_service.test.ts index 3a768744fa4..c7c2d359534 100644 --- a/packages/performance/src/services/iid_service.test.ts +++ b/packages/performance/src/services/iid_service.test.ts @@ -17,7 +17,6 @@ import { stub } from 'sinon'; import { expect } from 'chai'; -import { SettingsService } from './settings_service'; import { getIid, getIidPromise, @@ -25,24 +24,25 @@ import { getAuthTokenPromise } from './iid_service'; import '../../test/setup'; -import { FirebaseInstallations } from '@firebase/installations-types'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; describe('Firebase Perofmrance > iid_service', () => { const IID = 'fid'; const AUTH_TOKEN = 'authToken'; + let fakeInstallations: _FirebaseInstallationsInternal; before(() => { const getId = stub().resolves(IID); const getToken = stub().resolves(AUTH_TOKEN); - SettingsService.prototype.installationsService = ({ + fakeInstallations = ({ getId, getToken - } as unknown) as FirebaseInstallations; + } as unknown) as _FirebaseInstallationsInternal; }); describe('getIidPromise', () => { it('provides iid', async () => { - const iid = await getIidPromise(); + const iid = await getIidPromise(fakeInstallations); expect(iid).to.be.equal(IID); expect(getIid()).to.be.equal(IID); @@ -51,7 +51,7 @@ describe('Firebase Perofmrance > iid_service', () => { describe('getAuthTokenPromise', () => { it('provides authentication token', async () => { - const token = await getAuthTokenPromise(); + const token = await getAuthTokenPromise(fakeInstallations); expect(token).to.be.equal(AUTH_TOKEN); expect(getAuthenticationToken()).to.be.equal(AUTH_TOKEN); diff --git a/packages/performance/src/services/iid_service.ts b/packages/performance/src/services/iid_service.ts index 5213900ba47..42db4c0a73a 100644 --- a/packages/performance/src/services/iid_service.ts +++ b/packages/performance/src/services/iid_service.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SettingsService } from './settings_service'; + +import { _FirebaseInstallationsInternal } from '@firebase/installations'; let iid: string | undefined; let authToken: string | undefined; -export function getIidPromise(): Promise { - const iidPromise = SettingsService.getInstance().installationsService.getId(); +export function getIidPromise( + installationsService: _FirebaseInstallationsInternal +): Promise { + const iidPromise = installationsService.getId(); // eslint-disable-next-line @typescript-eslint/no-floating-promises iidPromise.then((iidVal: string) => { iid = iidVal; @@ -33,8 +36,10 @@ export function getIid(): string | undefined { return iid; } -export function getAuthTokenPromise(): Promise { - const authTokenPromise = SettingsService.getInstance().installationsService.getToken(); +export function getAuthTokenPromise( + installationsService: _FirebaseInstallationsInternal +): Promise { + const authTokenPromise = installationsService.getToken(); // eslint-disable-next-line @typescript-eslint/no-floating-promises authTokenPromise.then((authTokenVal: string) => { authToken = authTokenVal; diff --git a/packages/performance/src/services/initialization_service.test.ts b/packages/performance/src/services/initialization_service.test.ts index 832081bdc3c..7f89ae021de 100644 --- a/packages/performance/src/services/initialization_service.test.ts +++ b/packages/performance/src/services/initialization_service.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,10 @@ import { isPerfInitialized } from './initialization_service'; import { setupApi } from './api_service'; -import { SettingsService } from './settings_service'; -import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseApp } from '@firebase/app'; import '../../test/setup'; +import { FirebaseInstallations } from '@firebase/installations-types'; +import { PerformanceController } from '../controllers/perf'; describe('Firebase Perofmrance > initialization_service', () => { const IID = 'fid'; @@ -32,14 +33,33 @@ describe('Firebase Perofmrance > initialization_service', () => { const getId = stub(); const getToken = stub(); + const fakeFirebaseConfig = { + apiKey: 'api-key', + authDomain: 'project-id.firebaseapp.com', + databaseURL: 'https://project-id.firebaseio.com', + projectId: 'project-id', + storageBucket: 'project-id.appspot.com', + messagingSenderId: 'sender-id', + appId: '1:111:web:a1234' + }; + + const fakeFirebaseApp = ({ + options: fakeFirebaseConfig + } as unknown) as FirebaseApp; + + const fakeInstallations = ({ + getId, + getToken + } as unknown) as FirebaseInstallations; + const performanceController = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + const mockWindow = { ...self }; mockWindow.document = { ...mockWindow.document, readyState: 'complete' }; beforeEach(() => { - SettingsService.prototype.firebaseAppInstance = ({ - installations: () => ({ getId, getToken }) - } as unknown) as FirebaseApp; - stub(self, 'fetch').resolves(new Response('{}')); mockWindow.localStorage = { ...mockWindow.localStorage, setItem: stub() }; @@ -49,7 +69,7 @@ describe('Firebase Perofmrance > initialization_service', () => { it('changes initialization status after initialization is done', async () => { getId.resolves(IID); getToken.resolves(AUTH_TOKEN); - await getInitializationPromise(); + await getInitializationPromise(performanceController); expect(isPerfInitialized()).to.be.true; }); @@ -58,7 +78,7 @@ describe('Firebase Perofmrance > initialization_service', () => { getId.resolves(IID); getToken.resolves(AUTH_TOKEN); // eslint-disable-next-line @typescript-eslint/no-floating-promises - getInitializationPromise(); + getInitializationPromise(performanceController); expect(isPerfInitialized()).to.be.false; }); diff --git a/packages/performance/src/services/initialization_service.ts b/packages/performance/src/services/initialization_service.ts index 96226867122..94a1dfab1e5 100644 --- a/packages/performance/src/services/initialization_service.ts +++ b/packages/performance/src/services/initialization_service.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import { getIidPromise } from './iid_service'; import { getConfig } from './remote_config_service'; import { Api } from './api_service'; +import { PerformanceController } from '../controllers/perf'; const enum InitializationStatus { notInitialized = 1, @@ -29,10 +30,13 @@ let initializationStatus = InitializationStatus.notInitialized; let initializationPromise: Promise | undefined; -export function getInitializationPromise(): Promise { +export function getInitializationPromise( + performanceController: PerformanceController +): Promise { initializationStatus = InitializationStatus.initializationPending; - initializationPromise = initializationPromise || initializePerf(); + initializationPromise = + initializationPromise || initializePerf(performanceController); return initializationPromise; } @@ -41,10 +45,12 @@ export function isPerfInitialized(): boolean { return initializationStatus === InitializationStatus.initialized; } -function initializePerf(): Promise { +function initializePerf( + performanceController: PerformanceController +): Promise { return getDocumentReadyComplete() - .then(() => getIidPromise()) - .then(iid => getConfig(iid)) + .then(() => getIidPromise(performanceController.installations)) + .then(iid => getConfig(performanceController, iid)) .then( () => changeInitializationStatus(), () => changeInitializationStatus() diff --git a/packages/performance/src/services/oob_resources_service.test.ts b/packages/performance/src/services/oob_resources_service.test.ts index 9534d7aa087..cc1a4ab5201 100644 --- a/packages/performance/src/services/oob_resources_service.test.ts +++ b/packages/performance/src/services/oob_resources_service.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,9 +27,11 @@ import { expect } from 'chai'; import { Api, setupApi, EntryType } from './api_service'; import * as iidService from './iid_service'; import { setupOobResources } from './oob_resources_service'; -import { createNetworkRequestEntry } from '../resources/network_request'; import { Trace } from '../resources/trace'; import '../../test/setup'; +import { PerformanceController } from '../controllers/perf'; +import { FirebaseApp } from '@firebase/app'; +import { FirebaseInstallations } from '@firebase/installations-types'; describe('Firebase Performance > oob_resources_service', () => { const MOCK_ID = 'idasdfsffe'; @@ -85,13 +87,38 @@ describe('Firebase Performance > oob_resources_service', () => { void >; let createOobTraceStub: SinonStub< - [PerformanceNavigationTiming[], PerformanceEntry[], (number | undefined)?], + [ + PerformanceController, + PerformanceNavigationTiming[], + PerformanceEntry[], + (number | undefined)? + ], void >; let clock: SinonFakeTimers; setupApi(self); + const fakeFirebaseConfig = { + apiKey: 'api-key', + authDomain: 'project-id.firebaseapp.com', + databaseURL: 'https://project-id.firebaseio.com', + projectId: 'project-id', + storageBucket: 'project-id.appspot.com', + messagingSenderId: 'sender-id', + appId: '1:111:web:a1234' + }; + + const fakeFirebaseApp = ({ + options: fakeFirebaseConfig + } as unknown) as FirebaseApp; + + const fakeInstallations = ({} as unknown) as FirebaseInstallations; + const performanceController = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + beforeEach(() => { getIidStub = stub(iidService, 'getIid'); apiGetInstanceSpy = spy(Api, 'getInstance'); @@ -115,33 +142,31 @@ describe('Firebase Performance > oob_resources_service', () => { describe('setupOobResources', () => { it('does not start if there is no iid', () => { getIidStub.returns(undefined); - setupOobResources(); + setupOobResources(performanceController); expect(apiGetInstanceSpy).not.to.be.called; }); it('sets up network request collection', () => { getIidStub.returns(MOCK_ID); - setupOobResources(); + setupOobResources(performanceController); clock.tick(1); expect(apiGetInstanceSpy).to.be.called; expect(getEntriesByTypeStub).to.be.calledWith('resource'); - expect(setupObserverStub).to.be.calledWithExactly( - 'resource', - createNetworkRequestEntry - ); + expect(setupObserverStub).to.be.calledWith('resource'); }); it('sets up page load trace collection', () => { getIidStub.returns(MOCK_ID); - setupOobResources(); + setupOobResources(performanceController); clock.tick(1); expect(apiGetInstanceSpy).to.be.called; expect(getEntriesByTypeStub).to.be.calledWith('navigation'); expect(getEntriesByTypeStub).to.be.calledWith('paint'); expect(createOobTraceStub).to.be.calledWithExactly( + performanceController, [NAVIGATION_PERFORMANCE_ENTRY], [PAINT_PERFORMANCE_ENTRY] ); @@ -152,13 +177,14 @@ describe('Firebase Performance > oob_resources_service', () => { const api = Api.getInstance(); //@ts-ignore Assignment to read-only property. api.onFirstInputDelay = stub(); - setupOobResources(); + setupOobResources(performanceController); clock.tick(1); expect(api.onFirstInputDelay).to.be.called; expect(createOobTraceStub).not.to.be.called; clock.tick(5000); expect(createOobTraceStub).to.be.calledWithExactly( + performanceController, [NAVIGATION_PERFORMANCE_ENTRY], [PAINT_PERFORMANCE_ENTRY] ); @@ -175,11 +201,12 @@ describe('Firebase Performance > oob_resources_service', () => { api.onFirstInputDelay = (cb: FirstInputDelayCallback) => { firstInputDelayCallback = cb; }; - setupOobResources(); + setupOobResources(performanceController); clock.tick(1); firstInputDelayCallback(FIRST_INPUT_DELAY); expect(createOobTraceStub).to.be.calledWithExactly( + performanceController, [NAVIGATION_PERFORMANCE_ENTRY], [PAINT_PERFORMANCE_ENTRY], FIRST_INPUT_DELAY @@ -188,7 +215,7 @@ describe('Firebase Performance > oob_resources_service', () => { it('sets up user timing traces', () => { getIidStub.returns(MOCK_ID); - setupOobResources(); + setupOobResources(performanceController); clock.tick(1); expect(apiGetInstanceSpy).to.be.called; diff --git a/packages/performance/src/services/oob_resources_service.ts b/packages/performance/src/services/oob_resources_service.ts index 0e01236903e..66891e774fe 100644 --- a/packages/performance/src/services/oob_resources_service.ts +++ b/packages/performance/src/services/oob_resources_service.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,36 +14,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Api } from './api_service'; import { Trace } from '../resources/trace'; import { createNetworkRequestEntry } from '../resources/network_request'; import { TRACE_MEASURE_PREFIX } from '../constants'; import { getIid } from './iid_service'; +import { PerformanceController } from '../controllers/perf'; const FID_WAIT_TIME_MS = 5000; -export function setupOobResources(): void { +export function setupOobResources( + performanceController: PerformanceController +): void { // Do not initialize unless iid is available. if (!getIid()) { return; } // The load event might not have fired yet, and that means performance navigation timing // object has a duration of 0. The setup should run after all current tasks in js queue. - setTimeout(() => setupOobTraces(), 0); - setTimeout(() => setupNetworkRequests(), 0); - setTimeout(() => setupUserTimingTraces(), 0); + setTimeout(() => setupOobTraces(performanceController), 0); + setTimeout(() => setupNetworkRequests(performanceController), 0); + setTimeout(() => setupUserTimingTraces(performanceController), 0); } -function setupNetworkRequests(): void { +function setupNetworkRequests( + performanceController: PerformanceController +): void { const api = Api.getInstance(); const resources = api.getEntriesByType('resource'); for (const resource of resources) { - createNetworkRequestEntry(resource); + createNetworkRequestEntry(performanceController, resource); } - api.setupObserver('resource', createNetworkRequestEntry); + api.setupObserver('resource', entry => + createNetworkRequestEntry(performanceController, entry) + ); } -function setupOobTraces(): void { +function setupOobTraces(performanceController: PerformanceController): void { const api = Api.getInstance(); const navigationTimings = api.getEntriesByType( 'navigation' @@ -55,32 +63,52 @@ function setupOobTraces(): void { // If the fid call back is not called for certain time, continue without it. // eslint-disable-next-line @typescript-eslint/no-explicit-any let timeoutId: any = setTimeout(() => { - Trace.createOobTrace(navigationTimings, paintTimings); + Trace.createOobTrace( + performanceController, + navigationTimings, + paintTimings + ); timeoutId = undefined; }, FID_WAIT_TIME_MS); api.onFirstInputDelay((fid: number) => { if (timeoutId) { clearTimeout(timeoutId); - Trace.createOobTrace(navigationTimings, paintTimings, fid); + Trace.createOobTrace( + performanceController, + navigationTimings, + paintTimings, + fid + ); } }); } else { - Trace.createOobTrace(navigationTimings, paintTimings); + Trace.createOobTrace( + performanceController, + navigationTimings, + paintTimings + ); } } -function setupUserTimingTraces(): void { +function setupUserTimingTraces( + performanceController: PerformanceController +): void { const api = Api.getInstance(); // Run through the measure performance entries collected up to this point. const measures = api.getEntriesByType('measure'); for (const measure of measures) { - createUserTimingTrace(measure); + createUserTimingTrace(performanceController, measure); } // Setup an observer to capture the measures from this point on. - api.setupObserver('measure', createUserTimingTrace); + api.setupObserver('measure', entry => + createUserTimingTrace(performanceController, entry) + ); } -function createUserTimingTrace(measure: PerformanceEntry): void { +function createUserTimingTrace( + performanceController: PerformanceController, + measure: PerformanceEntry +): void { const measureName = measure.name; // Do not create a trace, if the user timing marks and measures are created by the sdk itself. if ( @@ -89,5 +117,5 @@ function createUserTimingTrace(measure: PerformanceEntry): void { ) { return; } - Trace.createUserTimingTrace(measureName); + Trace.createUserTimingTrace(performanceController, measureName); } diff --git a/packages/performance/src/services/perf_logger.test.ts b/packages/performance/src/services/perf_logger.test.ts index e11ec7a3d34..964641786da 100644 --- a/packages/performance/src/services/perf_logger.test.ts +++ b/packages/performance/src/services/perf_logger.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,15 @@ import * as iidService from './iid_service'; import { expect } from 'chai'; import { Api, setupApi } from './api_service'; import { SettingsService } from './settings_service'; -import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseApp } from '@firebase/app'; import * as initializationService from './initialization_service'; import { SDK_VERSION } from '../constants'; import * as attributeUtils from '../utils/attributes_utils'; import { createNetworkRequestEntry } from '../resources/network_request'; import '../../test/setup'; import { mergeStrings } from '../utils/string_merger'; +import { FirebaseInstallations } from '@firebase/installations-types'; +import { PerformanceController } from '../controllers/perf'; describe('Performance Monitoring > perf_logger', () => { const IID = 'idasdfsffe'; @@ -68,6 +70,15 @@ describe('Performance Monitoring > perf_logger', () => { } setupApi(self); + const fakeFirebaseApp = ({ + options: { appId: APP_ID } + } as unknown) as FirebaseApp; + + const fakeInstallations = ({} as unknown) as FirebaseInstallations; + const performanceController = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); beforeEach(() => { getIidStub = stub(iidService, 'getIid'); @@ -81,9 +92,6 @@ describe('Performance Monitoring > perf_logger', () => { stub(attributeUtils, 'getServiceWorkerStatus').returns( SERVICE_WORKER_STATUS ); - SettingsService.prototype.firebaseAppInstance = ({ - options: { appId: APP_ID } - } as unknown) as FirebaseApp; clock = useFakeTimers(); }); @@ -102,7 +110,7 @@ describe('Performance Monitoring > perf_logger', () => { initializationPromise ); - const trace = new Trace(TRACE_NAME); + const trace = new Trace(performanceController, TRACE_NAME); trace.record(START_TIME, DURATION); await initializationPromise.then(() => { clock.tick(1); @@ -123,7 +131,7 @@ describe('Performance Monitoring > perf_logger', () => { stub(initializationService, 'isPerfInitialized').returns(true); SettingsService.getInstance().loggingEnabled = true; SettingsService.getInstance().logTraceAfterSampling = true; - const trace = new Trace(TRACE_NAME); + const trace = new Trace(performanceController, TRACE_NAME); trace.putAttribute('attr', 'val'); trace.putMetric('counter1', 3); trace.record(START_TIME, DURATION); @@ -139,7 +147,7 @@ describe('Performance Monitoring > perf_logger', () => { stub(Api.prototype, 'requiredApisAvailable').returns(false); stub(attributeUtils, 'getVisibilityState').returns(VISIBILITY_STATE); stub(initializationService, 'isPerfInitialized').returns(true); - const trace = new Trace(TRACE_NAME); + const trace = new Trace(performanceController, TRACE_NAME); trace.record(START_TIME, DURATION); clock.tick(1); @@ -163,7 +171,7 @@ describe('Performance Monitoring > perf_logger', () => { stub(initializationService, 'isPerfInitialized').returns(true); SettingsService.getInstance().loggingEnabled = true; SettingsService.getInstance().logTraceAfterSampling = true; - const trace = new Trace(TRACE_NAME); + const trace = new Trace(performanceController, TRACE_NAME); for (let i = 1; i <= 32; i++) { trace.putMetric('counter' + i, i); } @@ -188,7 +196,7 @@ describe('Performance Monitoring > perf_logger', () => { stub(initializationService, 'isPerfInitialized').returns(true); SettingsService.getInstance().loggingEnabled = true; SettingsService.getInstance().logTraceAfterSampling = true; - const trace = new Trace(TRACE_NAME); + const trace = new Trace(performanceController, TRACE_NAME); for (let i = 1; i <= 5; i++) { trace.putAttribute('attr' + i, 'val' + i); } @@ -263,7 +271,12 @@ describe('Performance Monitoring > perf_logger', () => { firstContentfulPaint ]; - Trace.createOobTrace(navigationTimings, paintTimings, 90); + Trace.createOobTrace( + performanceController, + navigationTimings, + paintTimings, + 90 + ); clock.tick(1); expect(addToQueueStub).to.be.called; @@ -322,7 +335,10 @@ describe('Performance Monitoring > perf_logger', () => { SettingsService.getInstance().loggingEnabled = true; SettingsService.getInstance().logNetworkAfterSampling = true; // Calls logNetworkRequest under the hood. - createNetworkRequestEntry(RESOURCE_PERFORMANCE_ENTRY); + createNetworkRequestEntry( + performanceController, + RESOURCE_PERFORMANCE_ENTRY + ); clock.tick(1); expect(addToQueueStub).to.be.called; @@ -363,7 +379,10 @@ describe('Performance Monitoring > perf_logger', () => { SettingsService.getInstance().loggingEnabled = true; SettingsService.getInstance().logNetworkAfterSampling = true; // Calls logNetworkRequest under the hood. - createNetworkRequestEntry(CC_NETWORK_PERFORMANCE_ENTRY); + createNetworkRequestEntry( + performanceController, + CC_NETWORK_PERFORMANCE_ENTRY + ); clock.tick(1); expect(addToQueueStub).not.called; @@ -404,7 +423,10 @@ describe('Performance Monitoring > perf_logger', () => { SettingsService.getInstance().loggingEnabled = true; SettingsService.getInstance().logNetworkAfterSampling = true; // Calls logNetworkRequest under the hood. - createNetworkRequestEntry(FL_NETWORK_PERFORMANCE_ENTRY); + createNetworkRequestEntry( + performanceController, + FL_NETWORK_PERFORMANCE_ENTRY + ); clock.tick(1); expect(addToQueueStub).not.called; diff --git a/packages/performance/src/services/perf_logger.ts b/packages/performance/src/services/perf_logger.ts index 8c9053bdfea..74373b47f82 100644 --- a/packages/performance/src/services/perf_logger.ts +++ b/packages/performance/src/services/perf_logger.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ import { } from './initialization_service'; import { transportHandler } from './transport_service'; import { SDK_VERSION } from '../constants'; +import { FirebaseApp } from '@firebase/app'; +import { getAppId } from '../utils/app_utils'; const enum ResourceType { NetworkRequest, @@ -125,8 +127,7 @@ export function logTrace(trace: Trace): void { } else { // Custom traces can be used before the initialization but logging // should wait until after. - - getInitializationPromise().then( + getInitializationPromise(trace.performanceController).then( () => sendTraceLog(trace), () => sendTraceLog(trace) ); @@ -202,7 +203,9 @@ function serializeNetworkRequest(networkRequest: NetworkRequest): string { time_to_response_completed_us: networkRequest.timeToResponseCompletedUs }; const perfMetric: PerfNetworkLog = { - application_info: getApplicationInfo(), + application_info: getApplicationInfo( + networkRequest.performanceController.app + ), network_request_metric: networkRequestMetric }; return JSON.stringify(perfMetric); @@ -225,15 +228,15 @@ function serializeTrace(trace: Trace): string { } const perfMetric: PerfTraceLog = { - application_info: getApplicationInfo(), + application_info: getApplicationInfo(trace.performanceController.app), trace_metric: traceMetric }; return JSON.stringify(perfMetric); } -function getApplicationInfo(): ApplicationInfo { +function getApplicationInfo(firebaseApp: FirebaseApp): ApplicationInfo { return { - google_app_id: SettingsService.getInstance().getAppId(), + google_app_id: getAppId(firebaseApp), app_instance_id: getIid(), web_app_info: { sdk_version: SDK_VERSION, diff --git a/packages/performance/src/services/remote_config_service.test.ts b/packages/performance/src/services/remote_config_service.test.ts index 7bbcf3722d5..90a86ddd73e 100644 --- a/packages/performance/src/services/remote_config_service.test.ts +++ b/packages/performance/src/services/remote_config_service.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { stub, useFakeTimers, SinonFakeTimers, SinonStub } from 'sinon'; import { expect } from 'chai'; import { SettingsService } from './settings_service'; @@ -21,8 +22,10 @@ import { CONFIG_EXPIRY_LOCAL_STORAGE_KEY } from '../constants'; import { setupApi, Api } from './api_service'; import * as iidService from './iid_service'; import { getConfig } from './remote_config_service'; -import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseApp } from '@firebase/app'; import '../../test/setup'; +import { FirebaseInstallations } from '@firebase/installations-types'; +import { PerformanceController } from '../controllers/perf'; describe('Performance Monitoring > remote_config_service', () => { const IID = 'asd123'; @@ -49,6 +52,16 @@ describe('Performance Monitoring > remote_config_service', () => { setupApi(self); const ApiInstance = Api.getInstance(); + const fakeFirebaseApp = ({ + options: { projectId: PROJECT_ID, appId: APP_ID, apiKey: API_KEY } + } as unknown) as FirebaseApp; + + const fakeInstallations = ({} as unknown) as FirebaseInstallations; + const performanceController = new PerformanceController( + fakeFirebaseApp, + fakeInstallations + ); + function storageGetItemFakeFactory( expiry: string, config: string @@ -90,9 +103,6 @@ describe('Performance Monitoring > remote_config_service', () => { ); clock = useFakeTimers(GLOBAL_CLOCK_NOW); - SettingsService.prototype.firebaseAppInstance = ({ - options: { projectId: PROJECT_ID, appId: APP_ID, apiKey: API_KEY } - } as unknown) as FirebaseApp; // we need to stub the entire localStorage, because storage can't be stubbed in Firefox and IE. // stubbing on self(window) seems to only work the first time (at least in Firefox), the subsequent @@ -125,7 +135,7 @@ describe('Performance Monitoring > remote_config_service', () => { config: STRINGIFIED_CONFIG }); - await getConfig(IID); + await getConfig(performanceController, IID); expect(getItemStub).to.be.called; expect(SettingsService.getInstance().loggingEnabled).to.be.true; @@ -151,7 +161,7 @@ describe('Performance Monitoring > remote_config_service', () => { config: STRINGIFIED_CONFIG }); - await getConfig(IID); + await getConfig(performanceController, IID); expect(fetchStub).not.to.be.called; }); @@ -165,7 +175,7 @@ describe('Performance Monitoring > remote_config_service', () => { { reject: false, value: new Response(STRINGIFIED_CONFIG) } ); - await getConfig(IID); + await getConfig(performanceController, IID); expect(getItemStub).to.be.calledOnce; expect(SettingsService.getInstance().loggingEnabled).to.be.true; @@ -194,7 +204,7 @@ describe('Performance Monitoring > remote_config_service', () => { { reject: true } ); - await getConfig(IID); + await getConfig(performanceController, IID); expect(SettingsService.getInstance().loggingEnabled).to.equal(false); }); @@ -215,7 +225,7 @@ describe('Performance Monitoring > remote_config_service', () => { { reject: false, value: new Response(STRINGIFIED_PARTIAL_CONFIG) } ); - await getConfig(IID); + await getConfig(performanceController, IID); expect(SettingsService.getInstance().loggingEnabled).to.be.true; }); @@ -232,7 +242,7 @@ describe('Performance Monitoring > remote_config_service', () => { }, { reject: false, value: new Response(STRINGIFIED_PARTIAL_CONFIG) } ); - await getConfig(IID); + await getConfig(performanceController, IID); expect(SettingsService.getInstance().loggingEnabled).to.be.true; }); @@ -254,7 +264,7 @@ describe('Performance Monitoring > remote_config_service', () => { { reject: false, value: new Response(STRINGIFIED_CONFIG) } ); - await getConfig(IID); + await getConfig(performanceController, IID); expect(getItemStub).to.be.calledOnce; expect(SettingsService.getInstance().loggingEnabled).to.be.true; diff --git a/packages/performance/src/services/remote_config_service.ts b/packages/performance/src/services/remote_config_service.ts index 71f5266c805..558e995e9d6 100644 --- a/packages/performance/src/services/remote_config_service.ts +++ b/packages/performance/src/services/remote_config_service.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import { ERROR_FACTORY, ErrorCode } from '../utils/errors'; import { Api } from './api_service'; import { getAuthTokenPromise } from './iid_service'; import { SettingsService } from './settings_service'; +import { PerformanceController } from '../controllers/perf'; +import { getProjectId, getApiKey, getAppId } from '../utils/app_utils'; const REMOTE_CONFIG_SDK_VERSION = '0.0.1'; @@ -64,14 +66,17 @@ interface RemoteConfigResponse { const FIS_AUTH_PREFIX = 'FIREBASE_INSTALLATIONS_AUTH'; -export function getConfig(iid: string): Promise { +export function getConfig( + performanceController: PerformanceController, + iid: string +): Promise { const config = getStoredConfig(); if (config) { processConfig(config); return Promise.resolve(); } - return getRemoteConfig(iid) + return getRemoteConfig(performanceController, iid) .then(processConfig) .then( config => storeConfig(config), @@ -122,13 +127,15 @@ const COULD_NOT_GET_CONFIG_MSG = 'Could not fetch config, will use default configs'; function getRemoteConfig( + performanceController: PerformanceController, iid: string ): Promise { // Perf needs auth token only to retrieve remote config. - return getAuthTokenPromise() + return getAuthTokenPromise(performanceController.installations) .then(authToken => { - const projectId = SettingsService.getInstance().getProjectId(); - const configEndPoint = `https://firebaseremoteconfig.googleapis.com/v1/projects/${projectId}/namespaces/fireperf:fetch?key=${SettingsService.getInstance().getApiKey()}`; + const projectId = getProjectId(performanceController.app); + const apiKey = getApiKey(performanceController.app); + const configEndPoint = `https://firebaseremoteconfig.googleapis.com/v1/projects/${projectId}/namespaces/fireperf:fetch?key=${apiKey}`; const request = new Request(configEndPoint, { method: 'POST', headers: { Authorization: `${FIS_AUTH_PREFIX} ${authToken}` }, @@ -136,7 +143,7 @@ function getRemoteConfig( body: JSON.stringify({ app_instance_id: iid, app_instance_id_token: authToken, - app_id: SettingsService.getInstance().getAppId(), + app_id: getAppId(performanceController.app), app_version: SDK_VERSION, sdk_version: REMOTE_CONFIG_SDK_VERSION }) diff --git a/packages/performance/src/services/settings_service.ts b/packages/performance/src/services/settings_service.ts index 03540845883..83e08bd53d5 100644 --- a/packages/performance/src/services/settings_service.ts +++ b/packages/performance/src/services/settings_service.ts @@ -15,9 +15,6 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types'; -import { ERROR_FACTORY, ErrorCode } from '../utils/errors'; -import { FirebaseInstallations } from '@firebase/installations-types'; import { mergeStrings } from '../utils/string_merger'; let settingsServiceInstance: SettingsService | undefined; @@ -57,43 +54,6 @@ export class SettingsService { // TTL of config retrieved from remote config in hours. configTimeToLive = 12; - firebaseAppInstance!: FirebaseApp; - - installationsService!: FirebaseInstallations; - - getAppId(): string { - const appId = - this.firebaseAppInstance && - this.firebaseAppInstance.options && - this.firebaseAppInstance.options.appId; - if (!appId) { - throw ERROR_FACTORY.create(ErrorCode.NO_APP_ID); - } - return appId; - } - - getProjectId(): string { - const projectId = - this.firebaseAppInstance && - this.firebaseAppInstance.options && - this.firebaseAppInstance.options.projectId; - if (!projectId) { - throw ERROR_FACTORY.create(ErrorCode.NO_PROJECT_ID); - } - return projectId; - } - - getApiKey(): string { - const apiKey = - this.firebaseAppInstance && - this.firebaseAppInstance.options && - this.firebaseAppInstance.options.apiKey; - if (!apiKey) { - throw ERROR_FACTORY.create(ErrorCode.NO_API_KEY); - } - return apiKey; - } - getFlTransportFullUrl(): string { return this.flTransportEndpointUrl.concat('?key=', this.transportKey); } diff --git a/packages/performance/src/services/transport_service.test.ts b/packages/performance/src/services/transport_service.test.ts index caa1958aad4..43986e00817 100644 --- a/packages/performance/src/services/transport_service.test.ts +++ b/packages/performance/src/services/transport_service.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/src/services/transport_service.ts b/packages/performance/src/services/transport_service.ts index d57d8f5cf72..87f8efab773 100644 --- a/packages/performance/src/services/transport_service.ts +++ b/packages/performance/src/services/transport_service.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages-exp/performance-exp/src/utils/app_utils.ts b/packages/performance/src/utils/app_utils.ts similarity index 96% rename from packages-exp/performance-exp/src/utils/app_utils.ts rename to packages/performance/src/utils/app_utils.ts index 0e34c74b61c..4eb19526935 100644 --- a/packages-exp/performance-exp/src/utils/app_utils.ts +++ b/packages/performance/src/utils/app_utils.ts @@ -16,7 +16,7 @@ */ import { ERROR_FACTORY, ErrorCode } from './errors'; -import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseApp } from '@firebase/app'; export function getAppId(firebaseApp: FirebaseApp): string { const appId = firebaseApp.options?.appId; diff --git a/packages/performance/src/utils/attribute_utils.test.ts b/packages/performance/src/utils/attribute_utils.test.ts index 87b202dbc38..bda20b3e165 100644 --- a/packages/performance/src/utils/attribute_utils.test.ts +++ b/packages/performance/src/utils/attribute_utils.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/src/utils/attributes_utils.ts b/packages/performance/src/utils/attributes_utils.ts index c060f1de1f2..ef3f499e23b 100644 --- a/packages/performance/src/utils/attributes_utils.ts +++ b/packages/performance/src/utils/attributes_utils.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/src/utils/console_logger.ts b/packages/performance/src/utils/console_logger.ts index 8ba63d2879e..2f97aeb386b 100644 --- a/packages/performance/src/utils/console_logger.ts +++ b/packages/performance/src/utils/console_logger.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/src/utils/errors.ts b/packages/performance/src/utils/errors.ts index 5f33c95855d..83e55b3eed5 100644 --- a/packages/performance/src/utils/errors.ts +++ b/packages/performance/src/utils/errors.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,8 @@ export const enum ErrorCode { INVALID_ATTRIBUTE_NAME = 'invalid attribute name', INVALID_ATTRIBUTE_VALUE = 'invalid attribute value', INVALID_CUSTOM_METRIC_NAME = 'invalid custom metric name', - INVALID_STRING_MERGER_PARAMETER = 'invalid String merger input' + INVALID_STRING_MERGER_PARAMETER = 'invalid String merger input', + ALREADY_INITIALIZED = 'already initialized' } const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = { @@ -58,7 +59,12 @@ const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = { [ErrorCode.INVALID_CUSTOM_METRIC_NAME]: 'Custom metric name {$customMetricName} is invalid', [ErrorCode.INVALID_STRING_MERGER_PARAMETER]: - 'Input for String merger is invalid, contact support team to resolve.' + 'Input for String merger is invalid, contact support team to resolve.', + [ErrorCode.ALREADY_INITIALIZED]: + 'initializePerformance() has already been called with ' + + 'different options. To avoid this error, call initializePerformance() with the ' + + 'same options as when it was originally called, or call getPerformance() to return the' + + ' already initialized instance.' }; interface ErrorParams { diff --git a/packages/performance/src/utils/metric_utils.test.ts b/packages/performance/src/utils/metric_utils.test.ts index 0dee88521e3..fea213911f7 100644 --- a/packages/performance/src/utils/metric_utils.test.ts +++ b/packages/performance/src/utils/metric_utils.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/src/utils/metric_utils.ts b/packages/performance/src/utils/metric_utils.ts index aad0357114c..9bbc4886aef 100644 --- a/packages/performance/src/utils/metric_utils.ts +++ b/packages/performance/src/utils/metric_utils.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/src/utils/string_merger.test.ts b/packages/performance/src/utils/string_merger.test.ts index e192f8438f7..a82799f315e 100644 --- a/packages/performance/src/utils/string_merger.test.ts +++ b/packages/performance/src/utils/string_merger.test.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,11 @@ import { expect } from 'chai'; import { mergeStrings } from './string_merger'; import { FirebaseError } from '@firebase/util'; -// import { ERROR_FACTORY, ErrorCode } from './errors'; import '../../test/setup'; describe('Firebase Performance > string_merger', () => { describe('#mergeStrings', () => { it('Throws exception when string length has | diff | > 1', () => { - // const expectedError = ERROR_FACTORY.create(ErrorCode.INVALID_STRING_MERGER_PARAMETER); expect(() => mergeStrings('', '123')).to.throw( FirebaseError, 'performance/invalid String merger input' diff --git a/packages/performance/src/utils/string_merger.ts b/packages/performance/src/utils/string_merger.ts index d26295f7b11..620488a7416 100644 --- a/packages/performance/src/utils/string_merger.ts +++ b/packages/performance/src/utils/string_merger.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/performance/test/setup.ts b/packages/performance/test/setup.ts index 1b4641c59a8..11f8e4cec16 100644 --- a/packages/performance/test/setup.ts +++ b/packages/performance/test/setup.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/polyfill/README.md b/packages/polyfill/README.md deleted file mode 100644 index d30871dcfb7..00000000000 --- a/packages/polyfill/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# @firebase/polyfill - -This is the a set of polyfills/shims used by the Firebase JS SDK. This package -is completely standalone and can be loaded to standardize environments for use -with the Firebase JS SDK. - -**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages/polyfill/index.ts b/packages/polyfill/index.ts deleted file mode 100644 index f3c9d3bd9ae..00000000000 --- a/packages/polyfill/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Fetch -import 'whatwg-fetch'; - -// Promise -import 'core-js/features/promise'; - -// ES Stable -import 'core-js/features/array/find'; -import 'core-js/features/array/find-index'; -import 'core-js/features/array/from'; -import 'core-js/features/array/some'; -import 'core-js/features/typed-array/iterator'; -import 'core-js/features/object/assign'; -import 'core-js/features/object/entries'; -import 'core-js/features/object/values'; -import 'core-js/features/string/includes'; -import 'core-js/features/string/starts-with'; -import 'core-js/features/string/repeat'; -import 'core-js/features/symbol'; -import 'core-js/features/symbol/iterator'; -import 'core-js/features/map'; -import 'core-js/features/set'; -import 'core-js/features/number/is-integer'; -import 'core-js/features/number/is-nan'; diff --git a/packages/polyfill/package.json b/packages/polyfill/package.json deleted file mode 100644 index 2c7c46ff670..00000000000 --- a/packages/polyfill/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@firebase/polyfill", - "version": "0.3.36", - "description": "", - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "files": [ - "dist" - ], - "scripts": { - "build": "rollup -c", - "dev": "rollup -c -w", - "test": "echo 'No test suite for polyfills'", - "test:ci": "echo 'No test suite for polyfills'" - }, - "license": "Apache-2.0", - "dependencies": { - "core-js": "3.15.1", - "promise-polyfill": "8.2.0", - "whatwg-fetch": "2.0.4" - }, - "devDependencies": { - "rollup": "2.52.2", - "rollup-plugin-typescript2": "0.30.0" - }, - "repository": { - "directory": "packages/polyfill", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "typings": "dist/index.d.ts" -} diff --git a/packages/polyfill/rollup.config.js b/packages/polyfill/rollup.config.js deleted file mode 100644 index dd0f7b5c717..00000000000 --- a/packages/polyfill/rollup.config.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @license - * Copyright 2018 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import pkg from './package.json'; - -const plugins = [ - typescriptPlugin({ - typescript - }) -]; - -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); - -export default { - input: 'index.ts', - output: [ - { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } - ], - plugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) -}; diff --git a/packages-exp/installations-exp/.eslintrc.js b/packages/remote-config-compat/.eslintrc.js similarity index 100% rename from packages-exp/installations-exp/.eslintrc.js rename to packages/remote-config-compat/.eslintrc.js diff --git a/packages-exp/remote-config-compat/README.md b/packages/remote-config-compat/README.md similarity index 77% rename from packages-exp/remote-config-compat/README.md rename to packages/remote-config-compat/README.md index 2813f0ae9d4..dd27f72bd4b 100644 --- a/packages-exp/remote-config-compat/README.md +++ b/packages/remote-config-compat/README.md @@ -1,5 +1,5 @@ # @firebase/remote-compat -This is the compat package that recreates the v8 APIs. +This is the compat package that recreates the v8 APIs. **This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/remote-config-compat/karma.conf.js b/packages/remote-config-compat/karma.conf.js similarity index 100% rename from packages-exp/remote-config-compat/karma.conf.js rename to packages/remote-config-compat/karma.conf.js diff --git a/packages-exp/remote-config-compat/package.json b/packages/remote-config-compat/package.json similarity index 81% rename from packages-exp/remote-config-compat/package.json rename to packages/remote-config-compat/package.json index 04d17d3ed4e..1cf7ded0719 100644 --- a/packages-exp/remote-config-compat/package.json +++ b/packages/remote-config-compat/package.json @@ -3,7 +3,6 @@ "version": "0.0.900", "description": "The compatibility package of Remote Config", "author": "Firebase (https://firebase.google.com/)", - "private": true, "main": "dist/index.cjs.js", "browser": "dist/index.esm2017.js", "module": "dist/index.esm2017.js", @@ -14,7 +13,7 @@ "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "build": "rollup -c", - "build:release": "rollup -c rollup.config.release.js && yarn add-compat-overloads", + "build:release": "yarn build && yarn add-compat-overloads", "build:deps": "lerna run --scope @firebase/remote-config-compat --include-dependencies build", "dev": "rollup -c -w", "test": "run-p lint test:all", @@ -22,14 +21,14 @@ "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", "test:browser": "karma start --single-run", "test:browser:debug": "karma start --browsers Chrome --auto-watch", - "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../remote-config-exp/dist/remote-config-exp-public.d.ts -o dist/src/index.d.ts -a -r RemoteConfig:RemoteConfigCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/remote-config" + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../remote-config/dist/remote-config-public.d.ts -o dist/src/index.d.ts -a -r RemoteConfig:RemoteConfigCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/remote-config" }, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-compat": "0.x" }, "dependencies": { - "@firebase/remote-config-exp": "0.0.900", + "@firebase/remote-config": "0.1.43", "@firebase/remote-config-types": "0.1.9", "@firebase/util": "1.3.0", "@firebase/logger": "0.2.6", @@ -45,7 +44,7 @@ "@firebase/app-compat": "0.0.900" }, "repository": { - "directory": "packages-exp/remote-config-compat", + "directory": "packages/remote-config-compat", "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, diff --git a/packages-exp/performance-compat/rollup.shared.js b/packages/remote-config-compat/rollup.config.js similarity index 61% rename from packages-exp/performance-compat/rollup.shared.js rename to packages/remote-config-compat/rollup.config.js index 6d5afe89732..13612393d50 100644 --- a/packages-exp/performance-compat/rollup.shared.js +++ b/packages/remote-config-compat/rollup.config.js @@ -15,14 +15,23 @@ * limitations under the License. */ +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; import pkg from './package.json'; -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/performance' +const deps = Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)); +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() ]; -export const es5BuildsNoPlugin = [ +const es5Builds = [ /** * Browser Builds */ @@ -32,11 +41,29 @@ export const es5BuildsNoPlugin = [ { file: pkg.main, format: 'cjs', sourcemap: true }, { file: pkg.esm5, format: 'es', sourcemap: true } ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins } ]; -export const es2017BuildsNoPlugin = [ +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = [ /** * Browser Builds */ @@ -47,6 +74,9 @@ export const es2017BuildsNoPlugin = [ format: 'es', sourcemap: true }, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins } ]; + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-compat/src/index.ts b/packages/remote-config-compat/src/index.ts similarity index 87% rename from packages-exp/remote-config-compat/src/index.ts rename to packages/remote-config-compat/src/index.ts index 88de17406cf..ac9b2df6ff9 100644 --- a/packages-exp/remote-config-compat/src/index.ts +++ b/packages/remote-config-compat/src/index.ts @@ -26,13 +26,6 @@ import { RemoteConfigCompatImpl } from './remoteConfig'; import { name as packageName, version } from '../package.json'; import { RemoteConfig as RemoteConfigCompat } from '@firebase/remote-config-types'; -// TODO: move it to remote-config-types package -declare module '@firebase/component' { - interface NameServiceMapping { - 'remoteConfig-compat': RemoteConfigCompat; - } -} - function registerRemoteConfigCompat( firebaseInstance: _FirebaseNamespace ): void { @@ -52,8 +45,8 @@ function remoteConfigFactory( { instanceIdentifier: namespace }: InstanceFactoryOptions ): RemoteConfigCompatImpl { const app = container.getProvider('app-compat').getImmediate(); - // The following call will always succeed because rc `import {...} from '@firebase/remote-config-exp'` - const remoteConfig = container.getProvider('remote-config-exp').getImmediate({ + // The following call will always succeed because rc `import {...} from '@firebase/remote-config'` + const remoteConfig = container.getProvider('remote-config').getImmediate({ identifier: namespace }); diff --git a/packages-exp/remote-config-compat/src/remoteConfig.test.ts b/packages/remote-config-compat/src/remoteConfig.test.ts similarity index 98% rename from packages-exp/remote-config-compat/src/remoteConfig.test.ts rename to packages/remote-config-compat/src/remoteConfig.test.ts index 0d85990935e..3a9b51d57ee 100644 --- a/packages-exp/remote-config-compat/src/remoteConfig.test.ts +++ b/packages/remote-config-compat/src/remoteConfig.test.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import { RemoteConfigCompatImpl } from './remoteConfig'; import { getFakeApp, getFakeModularRemoteConfig } from '../test/util'; -import * as modularApi from '@firebase/remote-config-exp'; +import * as modularApi from '@firebase/remote-config'; describe('Remote Config Compat', () => { let remoteConfig!: RemoteConfigCompatImpl; diff --git a/packages-exp/remote-config-compat/src/remoteConfig.ts b/packages/remote-config-compat/src/remoteConfig.ts similarity index 98% rename from packages-exp/remote-config-compat/src/remoteConfig.ts rename to packages/remote-config-compat/src/remoteConfig.ts index 61b02ce62d8..af529b7a670 100644 --- a/packages-exp/remote-config-compat/src/remoteConfig.ts +++ b/packages/remote-config-compat/src/remoteConfig.ts @@ -35,7 +35,7 @@ import { getNumber, getString, getValue -} from '@firebase/remote-config-exp'; +} from '@firebase/remote-config'; export class RemoteConfigCompatImpl implements RemoteConfigCompat, _FirebaseService { diff --git a/packages-exp/performance-compat/test/setup.ts b/packages/remote-config-compat/test/setup.ts similarity index 100% rename from packages-exp/performance-compat/test/setup.ts rename to packages/remote-config-compat/test/setup.ts diff --git a/packages-exp/remote-config-compat/test/util.ts b/packages/remote-config-compat/test/util.ts similarity index 95% rename from packages-exp/remote-config-compat/test/util.ts rename to packages/remote-config-compat/test/util.ts index 387b6bdd4b1..fa0c1f39486 100644 --- a/packages-exp/remote-config-compat/test/util.ts +++ b/packages/remote-config-compat/test/util.ts @@ -16,7 +16,7 @@ */ import { FirebaseApp } from '@firebase/app-compat'; -import { RemoteConfig } from '@firebase/remote-config-exp'; +import { RemoteConfig } from '@firebase/remote-config'; export function getFakeApp(): FirebaseApp { return { diff --git a/packages-exp/remote-config-compat/tsconfig.json b/packages/remote-config-compat/tsconfig.json similarity index 100% rename from packages-exp/remote-config-compat/tsconfig.json rename to packages/remote-config-compat/tsconfig.json diff --git a/packages/remote-config-types/index.d.ts b/packages/remote-config-types/index.d.ts index 27b80975b6a..962248d9593 100644 --- a/packages/remote-config-types/index.d.ts +++ b/packages/remote-config-types/index.d.ts @@ -175,6 +175,6 @@ export type LogLevel = 'debug' | 'error' | 'silent'; declare module '@firebase/component' { interface NameServiceMapping { - 'remoteConfig': RemoteConfig; + 'remoteConfig-compat': RemoteConfig; } } diff --git a/packages/remote-config/.eslintrc.js b/packages/remote-config/.eslintrc.js index 981af65e71a..5a8c4b909c2 100644 --- a/packages/remote-config/.eslintrc.js +++ b/packages/remote-config/.eslintrc.js @@ -1,3 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + module.exports = { 'extends': '../../config/.eslintrc.js', 'parserOptions': { diff --git a/packages-exp/remote-config-exp/api-extractor.json b/packages/remote-config/api-extractor.json similarity index 100% rename from packages-exp/remote-config-exp/api-extractor.json rename to packages/remote-config/api-extractor.json diff --git a/packages/remote-config/index.ts b/packages/remote-config/index.ts deleted file mode 100644 index ba4ae9eb034..00000000000 --- a/packages/remote-config/index.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import firebase from '@firebase/app'; -import '@firebase/installations'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { RemoteConfig as RemoteConfigType } from '@firebase/remote-config-types'; -import { CachingClient } from './src/client/caching_client'; -import { RestClient } from './src/client/rest_client'; -import { RemoteConfig } from './src/remote_config'; -import { Storage } from './src/storage/storage'; -import { StorageCache } from './src/storage/storage_cache'; -import { ERROR_FACTORY, ErrorCode } from './src/errors'; -import { RetryingClient } from './src/client/retrying_client'; -import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger'; -import { name as packageName, version } from './package.json'; -import { - Component, - ComponentType, - ComponentContainer, - InstanceFactoryOptions -} from '@firebase/component'; - -// Facilitates debugging by enabling settings changes without rebuilding asset. -// Note these debug options are not part of a documented, supported API and can change at any time. -// Consolidates debug options for easier discovery. -// Uses transient variables on window to avoid lingering state causing panic. -declare global { - interface Window { - FIREBASE_REMOTE_CONFIG_URL_BASE: string; - } -} - -export function registerRemoteConfig( - firebaseInstance: _FirebaseNamespace -): void { - firebaseInstance.INTERNAL.registerComponent( - new Component( - 'remoteConfig', - remoteConfigFactory, - ComponentType.PUBLIC - ).setMultipleInstances(true) - ); - - firebaseInstance.registerVersion(packageName, version); - - function remoteConfigFactory( - container: ComponentContainer, - { instanceIdentifier: namespace }: InstanceFactoryOptions - ): RemoteConfig { - /* Dependencies */ - // getImmediate for FirebaseApp will always succeed - const app = container.getProvider('app').getImmediate(); - // The following call will always succeed because rc has `import '@firebase/installations'` - const installations = container.getProvider('installations').getImmediate(); - - // Guards against the SDK being used in non-browser environments. - if (typeof window === 'undefined') { - throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_WINDOW); - } - - // Normalizes optional inputs. - const { projectId, apiKey, appId } = app.options; - if (!projectId) { - throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_PROJECT_ID); - } - if (!apiKey) { - throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_API_KEY); - } - if (!appId) { - throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_APP_ID); - } - namespace = namespace || 'firebase'; - - const storage = new Storage(appId, app.name, namespace); - const storageCache = new StorageCache(storage); - - const logger = new Logger(packageName); - - // Sets ERROR as the default log level. - // See RemoteConfig#setLogLevel for corresponding normalization to ERROR log level. - logger.logLevel = FirebaseLogLevel.ERROR; - - const restClient = new RestClient( - installations, - // Uses the JS SDK version, by which the RC package version can be deduced, if necessary. - firebaseInstance.SDK_VERSION, - namespace, - projectId, - apiKey, - appId - ); - const retryingClient = new RetryingClient(restClient, storage); - const cachingClient = new CachingClient( - retryingClient, - storage, - storageCache, - logger - ); - - const remoteConfigInstance = new RemoteConfig( - app, - cachingClient, - storageCache, - storage, - logger - ); - - // Starts warming cache. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - remoteConfigInstance.ensureInitialized(); - - return remoteConfigInstance; - } -} - -registerRemoteConfig(firebase as _FirebaseNamespace); - -declare module '@firebase/app-types' { - interface FirebaseNamespace { - remoteConfig?: { - (app?: FirebaseApp): RemoteConfigType; - }; - } - interface FirebaseApp { - remoteConfig(): RemoteConfigType; - } -} diff --git a/packages/remote-config/package.json b/packages/remote-config/package.json index b3695278bde..2551d4df206 100644 --- a/packages/remote-config/package.json +++ b/packages/remote-config/package.json @@ -4,32 +4,35 @@ "description": "The Remote Config package of the Firebase JS SDK", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", "files": [ "dist" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "rollup -c", + "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/remote-config --include-dependencies build", + "build:release": "yarn build && yarn typings:public", "dev": "rollup -c -w", "test": "run-p lint test:browser", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", "test:browser": "karma start --single-run", "test:debug": "karma start --browsers=Chrome --auto-watch", - "prettier": "prettier --write '{src,test}/**/*.{js,ts}'" + "prettier": "prettier --write '{src,test}/**/*.{js,ts}'", + "api-report": "api-extractor run --local --verbose", + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/remote-config-public.d.ts", + "typings:internal": "node ../../scripts/exp/use_typings.js ./dist/src/index.d.ts" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" }, "dependencies": { "@firebase/installations": "0.4.32", "@firebase/logger": "0.2.6", - "@firebase/remote-config-types": "0.1.9", "@firebase/util": "1.3.0", "@firebase/component": "0.5.6", "tslib": "^2.1.0" @@ -49,11 +52,12 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/index.d.ts", + "typings": "dist/src/index.d.ts", "nyc": { "extension": [ ".ts" ], "reportDir": "./coverage/node" - } -} + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages/remote-config/rollup.config.js b/packages/remote-config/rollup.config.js index b0a656084d5..cc729f1a0da 100644 --- a/packages/remote-config/rollup.config.js +++ b/packages/remote-config/rollup.config.js @@ -20,10 +20,7 @@ import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; import pkg from './package.json'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); - +const deps = Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)); /** * ES5 Builds */ @@ -39,13 +36,13 @@ const es5Builds = [ * Browser Builds */ { - input: 'index.ts', + input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } + { file: pkg.esm5, format: 'es', sourcemap: true } ], - plugins: es5BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es5BuildPlugins } ]; @@ -65,18 +62,18 @@ const es2017BuildPlugins = [ ]; const es2017Builds = [ - /** - * Browser Builds - */ { - input: 'index.ts', + /** + * Browser Build + */ + input: 'src/index.ts', output: { - file: pkg.esm2017, + file: pkg.browser, format: 'es', sourcemap: true }, - plugins: es2017BuildPlugins, - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + plugins: es2017BuildPlugins } ]; diff --git a/packages-exp/remote-config-exp/src/api.ts b/packages/remote-config/src/api.ts similarity index 99% rename from packages-exp/remote-config-exp/src/api.ts rename to packages/remote-config/src/api.ts index bbad3130f7a..3bedf579548 100644 --- a/packages-exp/remote-config-exp/src/api.ts +++ b/packages/remote-config/src/api.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; +import { _getProvider, FirebaseApp, getApp } from '@firebase/app'; import { LogLevel as RemoteConfigLogLevel, RemoteConfig, diff --git a/packages-exp/remote-config-exp/src/api2.ts b/packages/remote-config/src/api2.ts similarity index 100% rename from packages-exp/remote-config-exp/src/api2.ts rename to packages/remote-config/src/api2.ts diff --git a/packages/remote-config/src/client/rest_client.ts b/packages/remote-config/src/client/rest_client.ts index ced3bee4d63..fe8c0156242 100644 --- a/packages/remote-config/src/client/rest_client.ts +++ b/packages/remote-config/src/client/rest_client.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { FirebaseInstallations } from '@firebase/installations-types'; import { FetchResponse, RemoteConfigFetchClient, @@ -24,6 +23,7 @@ import { } from './remote_config_fetch_client'; import { ERROR_FACTORY, ErrorCode } from '../errors'; import { getUserLanguage } from '../language'; +import { _FirebaseInstallationsInternal } from '@firebase/installations'; /** * Defines request body parameters required to call the fetch API: @@ -49,7 +49,7 @@ interface FetchRequestBody { */ export class RestClient implements RemoteConfigFetchClient { constructor( - private readonly firebaseInstallations: FirebaseInstallations, + private readonly firebaseInstallations: _FirebaseInstallationsInternal, private readonly sdkVersion: string, private readonly namespace: string, private readonly projectId: string, diff --git a/packages-exp/firebase-exp/database/index.ts b/packages/remote-config/src/constants.ts similarity index 92% rename from packages-exp/firebase-exp/database/index.ts rename to packages/remote-config/src/constants.ts index 912b6daaab2..365d9037f86 100644 --- a/packages-exp/firebase-exp/database/index.ts +++ b/packages/remote-config/src/constants.ts @@ -15,4 +15,4 @@ * limitations under the License. */ -export * from '@firebase/database'; +export const RC_COMPONENT_NAME = 'remote-config'; diff --git a/packages-exp/remote-config-exp/src/index.ts b/packages/remote-config/src/index.ts similarity index 100% rename from packages-exp/remote-config-exp/src/index.ts rename to packages/remote-config/src/index.ts diff --git a/packages-exp/remote-config-exp/src/public_types.ts b/packages/remote-config/src/public_types.ts similarity index 94% rename from packages-exp/remote-config-exp/src/public_types.ts rename to packages/remote-config/src/public_types.ts index b25004f8184..843841ef57d 100644 --- a/packages-exp/remote-config-exp/src/public_types.ts +++ b/packages/remote-config/src/public_types.ts @@ -15,12 +15,18 @@ * limitations under the License. */ +import { FirebaseApp } from '@firebase/app'; + /** * The Firebase Remote Config service interface. * * @public */ export interface RemoteConfig { + /** + * The {@link @firebase/app#FirebaseApp} this `RemoteConfig` instance is associated with. + */ + app: FirebaseApp; /** * Defines configuration for the Remote Config SDK. */ @@ -130,6 +136,6 @@ export type LogLevel = 'debug' | 'error' | 'silent'; declare module '@firebase/component' { interface NameServiceMapping { - 'remote-config-exp': RemoteConfig; + 'remote-config': RemoteConfig; } } diff --git a/packages-exp/remote-config-exp/src/register.ts b/packages/remote-config/src/register.ts similarity index 95% rename from packages-exp/remote-config-exp/src/register.ts rename to packages/remote-config/src/register.ts index ea71928ffcc..b06f76b380a 100644 --- a/packages-exp/remote-config-exp/src/register.ts +++ b/packages/remote-config/src/register.ts @@ -18,7 +18,7 @@ import { _registerComponent, registerVersion, SDK_VERSION -} from '@firebase/app-exp'; +} from '@firebase/app'; import { Component, ComponentType, @@ -39,7 +39,7 @@ import { Storage } from './storage/storage'; import { StorageCache } from './storage/storage_cache'; // This needs to be in the same file that calls `getProvider()` on the component // or it will get tree-shaken out. -import '@firebase/installations-exp'; +import '@firebase/installations'; export function registerRemoteConfig(): void { _registerComponent( @@ -58,10 +58,10 @@ export function registerRemoteConfig(): void { ): RemoteConfig { /* Dependencies */ // getImmediate for FirebaseApp will always succeed - const app = container.getProvider('app-exp').getImmediate(); + const app = container.getProvider('app').getImmediate(); // The following call will always succeed because rc has `import '@firebase/installations'` const installations = container - .getProvider('installations-exp-internal') + .getProvider('installations-internal') .getImmediate(); // Guards against the SDK being used in non-browser environments. diff --git a/packages/remote-config/src/remote_config.ts b/packages/remote-config/src/remote_config.ts index 3afec56b515..c7b1a3de5e1 100644 --- a/packages/remote-config/src/remote_config.ts +++ b/packages/remote-config/src/remote_config.ts @@ -15,23 +15,16 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseApp } from '@firebase/app'; import { RemoteConfig as RemoteConfigType, FetchStatus, - Settings, - Value as ValueType, - LogLevel as RemoteConfigLogLevel -} from '@firebase/remote-config-types'; + RemoteConfigSettings +} from './public_types'; import { StorageCache } from './storage/storage_cache'; -import { - RemoteConfigFetchClient, - RemoteConfigAbortSignal -} from './client/remote_config_fetch_client'; -import { Value } from './value'; -import { ErrorCode, hasErrorCode } from './errors'; +import { RemoteConfigFetchClient } from './client/remote_config_fetch_client'; import { Storage } from './storage/storage'; -import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger'; +import { Logger } from '@firebase/logger'; const DEFAULT_FETCH_TIMEOUT_MILLIS = 60 * 1000; // One minute const DEFAULT_CACHE_MAX_AGE_MILLIS = 12 * 60 * 60 * 1000; // Twelve hours. @@ -42,34 +35,25 @@ const DEFAULT_CACHE_MAX_AGE_MILLIS = 12 * 60 * 60 * 1000; // Twelve hours. * See {@link https://github.com/FirebasePrivate/firebase-js-sdk/blob/master/packages/firebase/index.d.ts|interface documentation} for method descriptions. */ export class RemoteConfig implements RemoteConfigType { - // Tracks completion of initialization promise. - private _isInitializationComplete = false; + /** + * Tracks completion of initialization promise. + * @internal + */ + _isInitializationComplete = false; - // De-duplicates initialization calls. - private _initializePromise?: Promise; + /** + * De-duplicates initialization calls. + * @internal + */ + _initializePromise?: Promise; - settings: Settings = { + settings: RemoteConfigSettings = { fetchTimeoutMillis: DEFAULT_FETCH_TIMEOUT_MILLIS, minimumFetchIntervalMillis: DEFAULT_CACHE_MAX_AGE_MILLIS }; defaultConfig: { [key: string]: string | number | boolean } = {}; - // Based on packages/firestore/src/util/log.ts but not static because we need per-instance levels - // to differentiate 2p and 3p use-cases. - setLogLevel(logLevel: RemoteConfigLogLevel): void { - switch (logLevel) { - case 'debug': - this._logger.logLevel = FirebaseLogLevel.DEBUG; - break; - case 'silent': - this._logger.logLevel = FirebaseLogLevel.SILENT; - break; - default: - this._logger.logLevel = FirebaseLogLevel.ERROR; - } - } - get fetchTimeMillis(): number { return this._storageCache.getLastSuccessfulFetchTimestampMillis() || -1; } @@ -84,136 +68,21 @@ export class RemoteConfig implements RemoteConfigType { // JS doesn't support private yet // (https://github.com/tc39/proposal-class-fields#private-fields), so we hint using an // underscore prefix. - private readonly _client: RemoteConfigFetchClient, - private readonly _storageCache: StorageCache, - private readonly _storage: Storage, - private readonly _logger: Logger + /** + * @internal + */ + readonly _client: RemoteConfigFetchClient, + /** + * @internal + */ + readonly _storageCache: StorageCache, + /** + * @internal + */ + readonly _storage: Storage, + /** + * @internal + */ + readonly _logger: Logger ) {} - - async activate(): Promise { - const [lastSuccessfulFetchResponse, activeConfigEtag] = await Promise.all([ - this._storage.getLastSuccessfulFetchResponse(), - this._storage.getActiveConfigEtag() - ]); - if ( - !lastSuccessfulFetchResponse || - !lastSuccessfulFetchResponse.config || - !lastSuccessfulFetchResponse.eTag || - lastSuccessfulFetchResponse.eTag === activeConfigEtag - ) { - // Either there is no successful fetched config, or is the same as current active - // config. - return false; - } - await Promise.all([ - this._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config), - this._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag) - ]); - return true; - } - - ensureInitialized(): Promise { - if (!this._initializePromise) { - this._initializePromise = this._storageCache - .loadFromStorage() - .then(() => { - this._isInitializationComplete = true; - }); - } - return this._initializePromise; - } - - /** - * @throws a {@link ErrorCode.FETCH_CLIENT_TIMEOUT} if the request takes longer than - * {@link Settings.fetchTimeoutInSeconds} or - * {@link DEFAULT_FETCH_TIMEOUT_SECONDS}. - */ - async fetch(): Promise { - // Aborts the request after the given timeout, causing the fetch call to - // reject with an AbortError. - // - //

Aborting after the request completes is a no-op, so we don't need a - // corresponding clearTimeout. - // - // Locating abort logic here because: - // * it uses a developer setting (timeout) - // * it applies to all retries (like curl's max-time arg) - // * it is consistent with the Fetch API's signal input - const abortSignal = new RemoteConfigAbortSignal(); - - setTimeout(async () => { - // Note a very low delay, eg < 10ms, can elapse before listeners are initialized. - abortSignal.abort(); - }, this.settings.fetchTimeoutMillis); - - // Catches *all* errors thrown by client so status can be set consistently. - try { - await this._client.fetch({ - cacheMaxAgeMillis: this.settings.minimumFetchIntervalMillis, - signal: abortSignal - }); - - await this._storageCache.setLastFetchStatus('success'); - } catch (e) { - const lastFetchStatus = hasErrorCode(e, ErrorCode.FETCH_THROTTLE) - ? 'throttle' - : 'failure'; - await this._storageCache.setLastFetchStatus(lastFetchStatus); - throw e; - } - } - - async fetchAndActivate(): Promise { - await this.fetch(); - return this.activate(); - } - - getAll(): { [key: string]: ValueType } { - return getAllKeys( - this._storageCache.getActiveConfig(), - this.defaultConfig - ).reduce((allConfigs, key) => { - allConfigs[key] = this.getValue(key); - return allConfigs; - }, {} as { [key: string]: ValueType }); - } - - getBoolean(key: string): boolean { - return this.getValue(key).asBoolean(); - } - - getNumber(key: string): number { - return this.getValue(key).asNumber(); - } - - getString(key: string): string { - return this.getValue(key).asString(); - } - - getValue(key: string): ValueType { - if (!this._isInitializationComplete) { - this._logger.debug( - `A value was requested for key "${key}" before SDK initialization completed.` + - ' Await on ensureInitialized if the intent was to get a previously activated value.' - ); - } - const activeConfig = this._storageCache.getActiveConfig(); - if (activeConfig && activeConfig[key] !== undefined) { - return new Value('remote', activeConfig[key]); - } else if (this.defaultConfig && this.defaultConfig[key] !== undefined) { - return new Value('default', String(this.defaultConfig[key])); - } - this._logger.debug( - `Returning static value for key "${key}".` + - ' Define a default or remote value if this is unintentional.' - ); - return new Value('static'); - } -} - -/** - * Dedupes and returns an array of all the keys of the received objects. - */ -function getAllKeys(obj1: {} = {}, obj2: {} = {}): string[] { - return Object.keys({ ...obj1, ...obj2 }); } diff --git a/packages/remote-config/test/remote_config.test.ts b/packages/remote-config/test/remote_config.test.ts index 5cd8a05b474..855a03e3fb2 100644 --- a/packages/remote-config/test/remote_config.test.ts +++ b/packages/remote-config/test/remote_config.test.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseApp } from '@firebase/app'; import { RemoteConfig as RemoteConfigType, LogLevel as RemoteConfigLogLevel -} from '@firebase/remote-config-types'; +} from '../src/public_types'; import { expect } from 'chai'; import * as sinon from 'sinon'; import { StorageCache } from '../src/storage/storage_cache'; @@ -33,6 +33,20 @@ import { Value } from '../src/value'; import './setup'; import { ERROR_FACTORY, ErrorCode } from '../src/errors'; import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger'; +import { + activate, + ensureInitialized, + getAll, + getBoolean, + getNumber, + getString, + getValue, + setLogLevel, + fetchConfig +} from '../src/api'; +import * as api from '../src/api'; +import { fetchAndActivate } from '../src'; +import { restore } from 'sinon'; describe('RemoteConfig', () => { const ACTIVE_CONFIG = { @@ -82,7 +96,7 @@ describe('RemoteConfig', () => { // Adapts getUserLanguage tests from packages/auth/test/utils_test.js for TypeScript. describe('setLogLevel', () => { it('proxies to the FirebaseLogger instance', () => { - rc.setLogLevel('debug'); + setLogLevel(rc, 'debug'); // Casts spy to any because property setters aren't defined on the SinonSpy type. expect(loggerLogLevelSpy.set).to.have.been.calledWith( @@ -92,7 +106,7 @@ describe('RemoteConfig', () => { it('normalizes levels other than DEBUG and SILENT to ERROR', () => { for (const logLevel of ['info', 'verbose', 'error', 'severe']) { - rc.setLogLevel(logLevel as RemoteConfigLogLevel); + setLogLevel(rc, logLevel as RemoteConfigLogLevel); // Casts spy to any because property setters aren't defined on the SinonSpy type. expect(loggerLogLevelSpy.set).to.have.been.calledWith( @@ -106,7 +120,7 @@ describe('RemoteConfig', () => { it('warms cache', async () => { storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve()); - await rc.ensureInitialized(); + await ensureInitialized(rc); expect(storageCache.loadFromStorage).to.have.been.calledOnce; }); @@ -114,8 +128,8 @@ describe('RemoteConfig', () => { it('de-duplicates repeated calls', async () => { storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve()); - await rc.ensureInitialized(); - await rc.ensureInitialized(); + await ensureInitialized(rc); + await ensureInitialized(rc); expect(storageCache.loadFromStorage).to.have.been.calledOnce; }); @@ -162,7 +176,7 @@ describe('RemoteConfig', () => { getActiveConfigStub.returns(ACTIVE_CONFIG); rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getValue('key1')).to.deep.eq( + expect(getValue(rc, 'key1')).to.deep.eq( new Value('remote', ACTIVE_CONFIG.key1) ); }); @@ -170,7 +184,7 @@ describe('RemoteConfig', () => { it('returns the default value if active is not available', () => { rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getValue('key1')).to.deep.eq( + expect(getValue(rc, 'key1')).to.deep.eq( new Value('default', DEFAULT_CONFIG.key1) ); }); @@ -179,10 +193,10 @@ describe('RemoteConfig', () => { const DEFAULTS = { trueVal: true, falseVal: false }; rc.defaultConfig = DEFAULTS; - expect(rc.getValue('trueVal')).to.deep.eq( + expect(getValue(rc, 'trueVal')).to.deep.eq( new Value('default', String(DEFAULTS.trueVal)) ); - expect(rc.getValue('falseVal')).to.deep.eq( + expect(getValue(rc, 'falseVal')).to.deep.eq( new Value('default', String(DEFAULTS.falseVal)) ); }); @@ -191,19 +205,19 @@ describe('RemoteConfig', () => { const DEFAULTS = { negative: -1, zero: 0, positive: 11 }; rc.defaultConfig = DEFAULTS; - expect(rc.getValue('negative')).to.deep.eq( + expect(getValue(rc, 'negative')).to.deep.eq( new Value('default', String(DEFAULTS.negative)) ); - expect(rc.getValue('zero')).to.deep.eq( + expect(getValue(rc, 'zero')).to.deep.eq( new Value('default', String(DEFAULTS.zero)) ); - expect(rc.getValue('positive')).to.deep.eq( + expect(getValue(rc, 'positive')).to.deep.eq( new Value('default', String(DEFAULTS.positive)) ); }); it('returns the static value if active and default are not available', () => { - expect(rc.getValue('key1')).to.deep.eq(new Value('static')); + expect(getValue(rc, 'key1')).to.deep.eq(new Value('static')); // Asserts debug message logged if static value is returned, per EAP feedback. expect(logger.debug).to.have.been.called; @@ -214,7 +228,7 @@ describe('RemoteConfig', () => { rc.defaultConfig = { key1: 'val' }; // Gets value before initialization. - rc.getValue('key1'); + getValue(rc, 'key1'); // Asserts getValue logs. expect(logger.debug).to.have.been.called; @@ -223,10 +237,10 @@ describe('RemoteConfig', () => { storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve()); // Ensures initialization completes. - await rc.ensureInitialized(); + await ensureInitialized(rc); // Gets value after initialization. - rc.getValue('key1'); + getValue(rc, 'key1'); // Asserts getValue doesn't log after initialization is complete. expect(logger.debug).to.have.been.calledOnce; @@ -238,17 +252,17 @@ describe('RemoteConfig', () => { getActiveConfigStub.returns(ACTIVE_CONFIG); rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getBoolean('key3')).to.be.true; + expect(getBoolean(rc, 'key3')).to.be.true; }); it('returns the default value if active is not available', () => { rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getBoolean('key3')).to.be.false; + expect(getBoolean(rc, 'key3')).to.be.false; }); it('returns the static value if active and default are not available', () => { - expect(rc.getBoolean('key3')).to.be.false; + expect(getBoolean(rc, 'key3')).to.be.false; }); }); @@ -257,17 +271,17 @@ describe('RemoteConfig', () => { getActiveConfigStub.returns(ACTIVE_CONFIG); rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getString('key1')).to.eq(ACTIVE_CONFIG.key1); + expect(getString(rc, 'key1')).to.eq(ACTIVE_CONFIG.key1); }); it('returns the default value if active is not available', () => { rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getString('key2')).to.eq(DEFAULT_CONFIG.key2); + expect(getString(rc, 'key2')).to.eq(DEFAULT_CONFIG.key2); }); it('returns the static value if active and default are not available', () => { - expect(rc.getString('key1')).to.eq(''); + expect(getString(rc, 'key1')).to.eq(''); }); }); @@ -276,17 +290,17 @@ describe('RemoteConfig', () => { getActiveConfigStub.returns(ACTIVE_CONFIG); rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getNumber('key4')).to.eq(Number(ACTIVE_CONFIG.key4)); + expect(getNumber(rc, 'key4')).to.eq(Number(ACTIVE_CONFIG.key4)); }); it('returns the default value if active is not available', () => { rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getNumber('key4')).to.eq(Number(DEFAULT_CONFIG.key4)); + expect(getNumber(rc, 'key4')).to.eq(Number(DEFAULT_CONFIG.key4)); }); it('returns the static value if active and default are not available', () => { - expect(rc.getNumber('key1')).to.eq(0); + expect(getNumber(rc, 'key1')).to.eq(0); }); }); @@ -295,7 +309,7 @@ describe('RemoteConfig', () => { getActiveConfigStub.returns(ACTIVE_CONFIG); rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getAll()).to.deep.eq({ + expect(getAll(rc)).to.deep.eq({ key1: new Value('remote', ACTIVE_CONFIG.key1), key2: new Value('remote', ACTIVE_CONFIG.key2), key3: new Value('remote', ACTIVE_CONFIG.key3), @@ -307,7 +321,7 @@ describe('RemoteConfig', () => { it('returns values in default if active is not available', () => { rc.defaultConfig = DEFAULT_CONFIG; - expect(rc.getAll()).to.deep.eq({ + expect(getAll(rc)).to.deep.eq({ key1: new Value('default', DEFAULT_CONFIG.key1), key2: new Value('default', DEFAULT_CONFIG.key2), key3: new Value('default', DEFAULT_CONFIG.key3), @@ -317,7 +331,7 @@ describe('RemoteConfig', () => { }); it('returns empty object if both active and default configs are not defined', () => { - expect(rc.getAll()).to.deep.eq({}); + expect(getAll(rc)).to.deep.eq({}); }); }); @@ -347,7 +361,7 @@ describe('RemoteConfig', () => { getLastSuccessfulFetchResponseStub.returns(Promise.resolve()); getActiveConfigEtagStub.returns(Promise.resolve(ETAG)); - const activateResponse = await rc.activate(); + const activateResponse = await activate(rc); expect(activateResponse).to.be.false; expect(storage.setActiveConfigEtag).to.not.have.been.called; @@ -360,7 +374,7 @@ describe('RemoteConfig', () => { ); getActiveConfigEtagStub.returns(Promise.resolve(ETAG)); - const activateResponse = await rc.activate(); + const activateResponse = await activate(rc); expect(activateResponse).to.be.false; expect(storage.setActiveConfigEtag).to.not.have.been.called; @@ -373,7 +387,7 @@ describe('RemoteConfig', () => { ); getActiveConfigEtagStub.returns(Promise.resolve(ETAG)); - const activateResponse = await rc.activate(); + const activateResponse = await activate(rc); expect(activateResponse).to.be.true; expect(storage.setActiveConfigEtag).to.have.been.calledWith(NEW_ETAG); @@ -386,7 +400,7 @@ describe('RemoteConfig', () => { ); getActiveConfigEtagStub.returns(Promise.resolve()); - const activateResponse = await rc.activate(); + const activateResponse = await activate(rc); expect(activateResponse).to.be.true; expect(storage.setActiveConfigEtag).to.have.been.calledWith(NEW_ETAG); @@ -395,39 +409,40 @@ describe('RemoteConfig', () => { }); describe('fetchAndActivate', () => { - let rcActivateStub: sinon.SinonStub<[], Promise>; + let rcActivateStub: sinon.SinonStub<[RemoteConfigType], Promise>; beforeEach(() => { - sinon.stub(rc, 'fetch').returns(Promise.resolve()); - rcActivateStub = sinon.stub(rc, 'activate'); + sinon.stub(api, 'fetchConfig').returns(Promise.resolve()); + rcActivateStub = sinon.stub(api, 'activate'); }); + + afterEach(() => restore()); + it('calls fetch and activate and returns activation boolean if true', async () => { rcActivateStub.returns(Promise.resolve(true)); - const response = await rc.fetchAndActivate(); + const response = await fetchAndActivate(rc); expect(response).to.be.true; - expect(rc.fetch).to.have.been.called; - expect(rc.activate).to.have.been.called; + expect(api.fetchConfig).to.have.been.calledWith(rc); + expect(api.activate).to.have.been.calledWith(rc); }); it('calls fetch and activate and returns activation boolean if false', async () => { rcActivateStub.returns(Promise.resolve(false)); - const response = await rc.fetchAndActivate(); + const response = await fetchAndActivate(rc); expect(response).to.be.false; - expect(rc.fetch).to.have.been.called; - expect(rc.activate).to.have.been.called; + expect(api.fetchConfig).to.have.been.calledWith(rc); + expect(api.activate).to.have.been.calledWith(rc); }); }); describe('fetch', () => { - let timeoutStub: sinon.SinonStub<[ - (...args: any[]) => void, - number, - ...any[] - ]>; + let timeoutStub: sinon.SinonStub< + [(...args: any[]) => void, number, ...any[]] + >; beforeEach(() => { client.fetch = sinon .stub() @@ -441,7 +456,7 @@ describe('RemoteConfig', () => { }); it('defines a default timeout', async () => { - await rc.fetch(); + await fetchConfig(rc); expect(timeoutStub).to.have.been.calledWith(sinon.match.any, 60000); }); @@ -449,7 +464,7 @@ describe('RemoteConfig', () => { it('honors a custom timeout', async () => { rc.settings.fetchTimeoutMillis = 1000; - await rc.fetch(); + await fetchConfig(rc); expect(timeoutStub).to.have.been.calledWith(sinon.match.any, 1000); }); @@ -460,7 +475,7 @@ describe('RemoteConfig', () => { .stub() .returns(Promise.resolve({ status } as FetchResponse)); - await rc.fetch(); + await fetchConfig(rc); expect(storageCache.setLastFetchStatus).to.have.been.calledWith( 'success' @@ -477,7 +492,7 @@ describe('RemoteConfig', () => { client.fetch = sinon.stub().returns(Promise.reject(error)); - const fetchPromise = rc.fetch(); + const fetchPromise = fetchConfig(rc); await expect(fetchPromise).to.eventually.be.rejectedWith(error); expect(storageCache.setLastFetchStatus).to.have.been.calledWith( @@ -494,7 +509,7 @@ describe('RemoteConfig', () => { client.fetch = sinon.stub().returns(Promise.reject(error)); - const fetchPromise = rc.fetch(); + const fetchPromise = fetchConfig(rc); await expect(fetchPromise).to.eventually.be.rejectedWith(error); expect(storageCache.setLastFetchStatus).to.have.been.calledWith( diff --git a/packages/remote-config/test_app/index.html b/packages/remote-config/test_app/index.html index 8bf6a0c888c..24de216b710 100644 --- a/packages/remote-config/test_app/index.html +++ b/packages/remote-config/test_app/index.html @@ -3,8 +3,8 @@ Test App - - + +